Module: PricingPlans::PlanOwner

Defined in:
lib/pricing_plans/plan_owner.rb

Overview

Mix-in for the configured plan owner class (e.g., Organization) Provides readable, owner-centric helpers.

Defined Under Namespace

Modules: ClassMethods, HasManyInterceptor

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

Syntactic sugar for feature checks: Allows calls like plan_owner.plan_allows_api_access? which map to plan_owner.plan_allows?(:api_access).



269
270
271
272
273
274
275
# File 'lib/pricing_plans/plan_owner.rb', line 269

def method_missing(method_name, *args, &block)
  if (m = method_name.to_s.match(/^plan_allows_(.+)\?$/))
    feature_key = m[1].to_sym
    return plan_allows?(feature_key)
  end
  super
end

Class Method Details

.define_limit_sugar_methods(plan_owner_class, limit_key) ⇒ Object

Define English-y sugar methods on the plan owner for a specific limit key. Idempotent: skips if methods already exist.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/pricing_plans/plan_owner.rb', line 14

def self.define_limit_sugar_methods(plan_owner_class, limit_key)
  key = limit_key.to_sym
  within_m = :"#{key}_within_plan_limits?"
  remaining_m = :"#{key}_remaining"
  percent_m = :"#{key}_percent_used"
  grace_active_m = :"#{key}_grace_active?"
  grace_ends_m = :"#{key}_grace_ends_at"
  blocked_m = :"#{key}_blocked?"

  unless plan_owner_class.method_defined?(within_m)
    plan_owner_class.define_method(within_m) do |by: 1|
      LimitChecker.within_limit?(self, key, by: by)
    end
  end

  unless plan_owner_class.method_defined?(remaining_m)
    plan_owner_class.define_method(remaining_m) do
      LimitChecker.plan_limit_remaining(self, key)
    end
  end

  unless plan_owner_class.method_defined?(percent_m)
    plan_owner_class.define_method(percent_m) do
      LimitChecker.plan_limit_percent_used(self, key)
    end
  end

  unless plan_owner_class.method_defined?(grace_active_m)
    plan_owner_class.define_method(grace_active_m) do
      GraceManager.grace_active?(self, key)
    end
  end

  unless plan_owner_class.method_defined?(grace_ends_m)
    plan_owner_class.define_method(grace_ends_m) do
      GraceManager.grace_ends_at(self, key)
    end
  end

  unless plan_owner_class.method_defined?(blocked_m)
    plan_owner_class.define_method(blocked_m) do
      GraceManager.should_block?(self, key)
    end
  end
end

.included(base) ⇒ Object



7
8
9
10
# File 'lib/pricing_plans/plan_owner.rb', line 7

def self.included(base)
  base.extend(ClassMethods)
  base.singleton_class.prepend HasManyInterceptor
end

Instance Method Details

#any_grace_active_for?(*limit_keys) ⇒ Boolean

Aggregate helpers across multiple limit keys

Returns:

  • (Boolean)


322
323
324
# File 'lib/pricing_plans/plan_owner.rb', line 322

def any_grace_active_for?(*limit_keys)
  limit_keys.flatten.any? { |k| GraceManager.grace_active?(self, k) }
end

#approaching_limit?(limit_key, at: nil) ⇒ Boolean

Returns:

  • (Boolean)


378
379
380
# File 'lib/pricing_plans/plan_owner.rb', line 378

def approaching_limit?(limit_key, at: nil)
  PricingPlans.approaching_limit?(self, limit_key, at: at)
end

#assign_pricing_plan!(plan_key, source: "manual") ⇒ Object



244
245
246
247
248
249
250
251
# File 'lib/pricing_plans/plan_owner.rb', line 244

def assign_pricing_plan!(plan_key, source: "manual")
  LegacyPlanAssignmentApi.create_or_update_override!(
    self,
    plan_key,
    source: source,
    called_method_name: "assign_pricing_plan!"
  )
end

#attention_required_for_limit?(limit_key) ⇒ Boolean

Returns:

  • (Boolean)


374
375
376
# File 'lib/pricing_plans/plan_owner.rb', line 374

def attention_required_for_limit?(limit_key)
  PricingPlans.attention_required?(self, limit_key)
end

#clear_pricing_plan_override!Object



240
241
242
# File 'lib/pricing_plans/plan_owner.rb', line 240

def clear_pricing_plan_override!
  PlanResolver.clear_pricing_plan_override_for!(self)
end

#current_pricing_planObject



197
198
199
# File 'lib/pricing_plans/plan_owner.rb', line 197

def current_pricing_plan
  PlanResolver.effective_plan_for(self)
end

#current_pricing_plan_resolutionObject



201
202
203
# File 'lib/pricing_plans/plan_owner.rb', line 201

def current_pricing_plan_resolution
  PlanResolver.resolution_for(self)
end

#current_pricing_plan_sourceObject



205
206
207
# File 'lib/pricing_plans/plan_owner.rb', line 205

def current_pricing_plan_source
  current_pricing_plan_resolution.source
end

#earliest_grace_ends_at_for(*limit_keys) ⇒ Object



326
327
328
329
# File 'lib/pricing_plans/plan_owner.rb', line 326

def earliest_grace_ends_at_for(*limit_keys)
  times = limit_keys.flatten.map { |k| GraceManager.grace_ends_at(self, k) }.compact
  times.min
end

#grace_active_for?(limit_key) ⇒ Boolean

Per-limit grace helpers managed by PricingPlans

Returns:

  • (Boolean)


299
300
301
# File 'lib/pricing_plans/plan_owner.rb', line 299

def grace_active_for?(limit_key)
  GraceManager.grace_active?(self, limit_key)
end

#grace_ends_at_for(limit_key) ⇒ Object



303
304
305
# File 'lib/pricing_plans/plan_owner.rb', line 303

def grace_ends_at_for(limit_key)
  GraceManager.grace_ends_at(self, limit_key)
end

#grace_remaining_days_for(limit_key) ⇒ Object



313
314
315
# File 'lib/pricing_plans/plan_owner.rb', line 313

def grace_remaining_days_for(limit_key)
  (grace_remaining_seconds_for(limit_key) / 86_400.0).ceil
end

#grace_remaining_seconds_for(limit_key) ⇒ Object



307
308
309
310
311
# File 'lib/pricing_plans/plan_owner.rb', line 307

def grace_remaining_seconds_for(limit_key)
  ends_at = grace_ends_at_for(limit_key)
  return 0 unless ends_at
  [0, (ends_at - Time.current).to_i].max
end

#has_plan_assignment?Boolean

Returns:

  • (Boolean)


214
215
216
# File 'lib/pricing_plans/plan_owner.rb', line 214

def has_plan_assignment?
  pricing_plan_overridden?
end

#limit(limit_key) ⇒ Object

Rails-y wrappers for usage/status (defaults to all configured limits)



332
333
334
# File 'lib/pricing_plans/plan_owner.rb', line 332

def limit(limit_key)
  PricingPlans.status(self, limits: [limit_key]).first
end

#limit_alert(limit_key) ⇒ Object

Returns the pure-data alert view model for a single limit key



388
389
390
# File 'lib/pricing_plans/plan_owner.rb', line 388

def limit_alert(limit_key)
  PricingPlans.alert_for(self, limit_key)
end

#limit_message(limit_key) ⇒ Object



366
367
368
# File 'lib/pricing_plans/plan_owner.rb', line 366

def limit_message(limit_key)
  PricingPlans.message_for(self, limit_key)
end

#limit_overage(limit_key) ⇒ Object



370
371
372
# File 'lib/pricing_plans/plan_owner.rb', line 370

def limit_overage(limit_key)
  PricingPlans.overage_for(self, limit_key)
end

#limit_severity(limit_key) ⇒ Object

View-friendly, English-like convenience helpers (pure data decisions)



362
363
364
# File 'lib/pricing_plans/plan_owner.rb', line 362

def limit_severity(limit_key)
  PricingPlans.severity_for(self, limit_key)
end

#limits(*limit_keys) ⇒ Object



336
337
338
339
# File 'lib/pricing_plans/plan_owner.rb', line 336

def limits(*limit_keys)
  keys = normalize_limit_keys(limit_keys)
  PricingPlans.status(self, limits: keys)
end

#limits_message(*limit_keys) ⇒ Object



350
351
352
353
# File 'lib/pricing_plans/plan_owner.rb', line 350

def limits_message(*limit_keys)
  keys = normalize_limit_keys(limit_keys)
  PricingPlans.combine_messages_for(self, *keys)
end

#limits_overview(*limit_keys) ⇒ Object

Global overview for banner building



356
357
358
359
# File 'lib/pricing_plans/plan_owner.rb', line 356

def limits_overview(*limit_keys)
  keys = normalize_limit_keys(limit_keys)
  PricingPlans.overview_for(self, *keys)
end

#limits_severity(*limit_keys) ⇒ Object



345
346
347
348
# File 'lib/pricing_plans/plan_owner.rb', line 345

def limits_severity(*limit_keys)
  keys = normalize_limit_keys(limit_keys)
  PricingPlans.highest_severity_for(self, *keys)
end

#limits_summary(*limit_keys) ⇒ Object



341
342
343
# File 'lib/pricing_plans/plan_owner.rb', line 341

def limits_summary(*limit_keys)
  limits(*limit_keys)
end

#on_free_plan?Boolean

Returns:

  • (Boolean)


209
210
211
212
# File 'lib/pricing_plans/plan_owner.rb', line 209

def on_free_plan?
  plan = current_pricing_plan || PricingPlans::Registry.default_plan
  plan&.free? || false
end

#override_pricing_plan!(plan_key, source:) ⇒ Object



236
237
238
# File 'lib/pricing_plans/plan_owner.rb', line 236

def override_pricing_plan!(plan_key, source:)
  PlanResolver.override_pricing_plan_for!(self, plan_key, source: source)
end

#pay_on_grace_period?Boolean

Returns:

  • (Boolean)


293
294
295
296
# File 'lib/pricing_plans/plan_owner.rb', line 293

def pay_on_grace_period?
  sub = PaySupport.current_subscription_for(self)
  !!(sub && sub.respond_to?(:on_grace_period?) && sub.on_grace_period?)
end

#pay_on_trial?Boolean

Returns:

  • (Boolean)


288
289
290
291
# File 'lib/pricing_plans/plan_owner.rb', line 288

def pay_on_trial?
  sub = PaySupport.current_subscription_for(self)
  !!(sub && sub.respond_to?(:on_trial?) && sub.on_trial?)
end

#pay_subscription_active?Boolean

Pay (Stripe) convenience wrappers (return false/nil if Pay not available) Pay (Stripe) state — billing-facing, NOT used by our enforcement logic

Returns:

  • (Boolean)


284
285
286
# File 'lib/pricing_plans/plan_owner.rb', line 284

def pay_subscription_active?
  PaySupport.subscription_active_for?(self)
end

#plan_allows?(feature_key) ⇒ Boolean

Features

Returns:

  • (Boolean)


261
262
263
264
# File 'lib/pricing_plans/plan_owner.rb', line 261

def plan_allows?(feature_key)
  plan = current_pricing_plan
  plan&.allows_feature?(feature_key) || false
end

#plan_assignmentObject



223
224
225
# File 'lib/pricing_plans/plan_owner.rb', line 223

def plan_assignment
  pricing_plan_override
end

#plan_blocked_for?(limit_key) ⇒ Boolean

Returns:

  • (Boolean)


317
318
319
# File 'lib/pricing_plans/plan_owner.rb', line 317

def plan_blocked_for?(limit_key)
  GraceManager.should_block?(self, limit_key)
end

#plan_ctaObject

Returns { text:, url: }



383
384
385
# File 'lib/pricing_plans/plan_owner.rb', line 383

def plan_cta
  PricingPlans.cta_for(self)
end

#plan_limit_percent_used(limit_key) ⇒ Object



193
194
195
# File 'lib/pricing_plans/plan_owner.rb', line 193

def plan_limit_percent_used(limit_key)
  LimitChecker.plan_limit_percent_used(self, limit_key)
end

#plan_limit_remaining(limit_key) ⇒ Object



189
190
191
# File 'lib/pricing_plans/plan_owner.rb', line 189

def plan_limit_remaining(limit_key)
  LimitChecker.plan_limit_remaining(self, limit_key)
end

#pricing_plan_overridden?Boolean

Returns:

  • (Boolean)


218
219
220
221
# File 'lib/pricing_plans/plan_owner.rb', line 218

def pricing_plan_overridden?
  return false unless respond_to?(:id) && id.present?
  Assignment.exists?(plan_owner_type: self.class.name, plan_owner_id: id)
end

#pricing_plan_overrideObject



227
228
229
230
# File 'lib/pricing_plans/plan_owner.rb', line 227

def pricing_plan_override
  return nil unless respond_to?(:id) && id.present?
  Assignment.find_by(plan_owner_type: self.class.name, plan_owner_id: id)
end

#pricing_plan_override_sourceObject



232
233
234
# File 'lib/pricing_plans/plan_owner.rb', line 232

def pricing_plan_override_source
  pricing_plan_override&.source
end

#remove_pricing_plan!Object



253
254
255
256
257
258
# File 'lib/pricing_plans/plan_owner.rb', line 253

def remove_pricing_plan!
  LegacyPlanAssignmentApi.clear_override!(
    self,
    called_method_name: "remove_pricing_plan!"
  )
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


277
278
279
280
# File 'lib/pricing_plans/plan_owner.rb', line 277

def respond_to_missing?(method_name, include_private = false)
  return true if method_name.to_s.start_with?("plan_allows_") && method_name.to_s.end_with?("?")
  super
end

#within_plan_limits?(limit_key, by: 1) ⇒ Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/pricing_plans/plan_owner.rb', line 185

def within_plan_limits?(limit_key, by: 1)
  LimitChecker.within_limit?(self, limit_key, by: by)
end