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).



241
242
243
244
245
246
247
# File 'lib/pricing_plans/plan_owner.rb', line 241

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)


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

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)


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

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

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



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

def assign_pricing_plan!(plan_key, source: "manual")
  Assignment.assign_plan_to(self, plan_key, source: source)
end

#attention_required_for_limit?(limit_key) ⇒ Boolean

Returns:

  • (Boolean)


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

def attention_required_for_limit?(limit_key)
  PricingPlans.attention_required?(self, limit_key)
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



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

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)


271
272
273
# File 'lib/pricing_plans/plan_owner.rb', line 271

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

#grace_ends_at_for(limit_key) ⇒ Object



275
276
277
# File 'lib/pricing_plans/plan_owner.rb', line 275

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

#grace_remaining_days_for(limit_key) ⇒ Object



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

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



279
280
281
282
283
# File 'lib/pricing_plans/plan_owner.rb', line 279

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
217
# File 'lib/pricing_plans/plan_owner.rb', line 214

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

#limit(limit_key) ⇒ Object

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



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

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



360
361
362
# File 'lib/pricing_plans/plan_owner.rb', line 360

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

#limit_message(limit_key) ⇒ Object



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

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

#limit_overage(limit_key) ⇒ Object



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

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)



334
335
336
# File 'lib/pricing_plans/plan_owner.rb', line 334

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

#limits(*limit_keys) ⇒ Object



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

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

#limits_message(*limit_keys) ⇒ Object



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

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



328
329
330
331
# File 'lib/pricing_plans/plan_owner.rb', line 328

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

#limits_severity(*limit_keys) ⇒ Object



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

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

#limits_summary(*limit_keys) ⇒ Object



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

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

#pay_on_grace_period?Boolean

Returns:

  • (Boolean)


265
266
267
268
# File 'lib/pricing_plans/plan_owner.rb', line 265

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)


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

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)


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

def pay_subscription_active?
  PaySupport.subscription_active_for?(self)
end

#plan_allows?(feature_key) ⇒ Boolean

Features

Returns:

  • (Boolean)


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

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

#plan_assignmentObject



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

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

#plan_blocked_for?(limit_key) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#plan_ctaObject

Returns { text:, url: }



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

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

#remove_pricing_plan!Object



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

def remove_pricing_plan!
  Assignment.remove_assignment_for(self)
end

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

Returns:

  • (Boolean)


249
250
251
252
# File 'lib/pricing_plans/plan_owner.rb', line 249

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