pricing_plans 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.simplecov +35 -0
- data/AGENTS.md +5 -0
- data/CHANGELOG.md +14 -1
- data/CLAUDE.md +5 -1
- data/README.md +2 -2
- data/docs/01-define-pricing-plans.md +104 -15
- data/docs/03-model-helpers.md +59 -0
- data/gemfiles/rails_7.2.gemfile +6 -4
- data/gemfiles/rails_8.1.gemfile +6 -4
- data/lib/generators/pricing_plans/install/templates/initializer.rb +38 -4
- data/lib/pricing_plans/callbacks.rb +152 -0
- data/lib/pricing_plans/configuration.rb +18 -6
- data/lib/pricing_plans/limitable.rb +38 -1
- data/lib/pricing_plans/models/enforcement_state.rb +1 -1
- data/lib/pricing_plans/period_calculator.rb +6 -0
- data/lib/pricing_plans/plan_owner.rb +80 -0
- data/lib/pricing_plans/plan_resolver.rb +13 -13
- data/lib/pricing_plans/registry.rb +2 -2
- data/lib/pricing_plans/status_context.rb +343 -0
- data/lib/pricing_plans/version.rb +1 -1
- data/lib/pricing_plans.rb +50 -10
- metadata +6 -3
- data/.claude/settings.local.json +0 -19
data/lib/pricing_plans.rb
CHANGED
|
@@ -28,6 +28,7 @@ module PricingPlans
|
|
|
28
28
|
autoload :LimitChecker, "pricing_plans/limit_checker"
|
|
29
29
|
autoload :LimitableRegistry, "pricing_plans/limit_checker"
|
|
30
30
|
autoload :GraceManager, "pricing_plans/grace_manager"
|
|
31
|
+
autoload :Callbacks, "pricing_plans/callbacks"
|
|
31
32
|
autoload :PeriodCalculator, "pricing_plans/period_calculator"
|
|
32
33
|
autoload :ControllerGuards, "pricing_plans/controller_guards"
|
|
33
34
|
autoload :JobGuards, "pricing_plans/job_guards"
|
|
@@ -39,6 +40,7 @@ module PricingPlans
|
|
|
39
40
|
autoload :OverageReporter, "pricing_plans/overage_reporter"
|
|
40
41
|
autoload :PriceComponents, "pricing_plans/price_components"
|
|
41
42
|
autoload :ViewHelpers, "pricing_plans/view_helpers"
|
|
43
|
+
autoload :StatusContext, "pricing_plans/status_context"
|
|
42
44
|
|
|
43
45
|
# Models
|
|
44
46
|
autoload :EnforcementState, "pricing_plans/models/enforcement_state"
|
|
@@ -227,8 +229,12 @@ module PricingPlans
|
|
|
227
229
|
)
|
|
228
230
|
|
|
229
231
|
def status(plan_owner, limits: [])
|
|
232
|
+
# Use StatusContext to cache all computed values and eliminate N+1 queries.
|
|
233
|
+
# Each call to status() gets its own context - thread-safe by design.
|
|
234
|
+
ctx = StatusContext.new(plan_owner)
|
|
235
|
+
|
|
230
236
|
items = Array(limits).map do |limit_key|
|
|
231
|
-
st = limit_status(limit_key
|
|
237
|
+
st = ctx.limit_status(limit_key)
|
|
232
238
|
if !st[:configured]
|
|
233
239
|
StatusItem.new(
|
|
234
240
|
key: limit_key,
|
|
@@ -257,7 +263,7 @@ module PricingPlans
|
|
|
257
263
|
period_seconds_remaining: nil
|
|
258
264
|
)
|
|
259
265
|
else
|
|
260
|
-
sev = severity_for(
|
|
266
|
+
sev = ctx.severity_for(limit_key)
|
|
261
267
|
allowed = st[:limit_amount]
|
|
262
268
|
current = st[:current_usage].to_i
|
|
263
269
|
unlimited = (allowed == :unlimited)
|
|
@@ -266,7 +272,7 @@ module PricingPlans
|
|
|
266
272
|
else
|
|
267
273
|
nil
|
|
268
274
|
end
|
|
269
|
-
warn_thresholds =
|
|
275
|
+
warn_thresholds = ctx.warning_thresholds(limit_key)
|
|
270
276
|
percent = st[:percent_used].to_f
|
|
271
277
|
next_warn = begin
|
|
272
278
|
thresholds = warn_thresholds.map { |t| t.to_f * 100.0 }.uniq.sort
|
|
@@ -277,7 +283,7 @@ module PricingPlans
|
|
|
277
283
|
period_seconds_remaining = nil
|
|
278
284
|
if st[:per]
|
|
279
285
|
begin
|
|
280
|
-
period_start, period_end =
|
|
286
|
+
period_start, period_end = ctx.period_window_for(limit_key)
|
|
281
287
|
if period_end
|
|
282
288
|
period_seconds_remaining = [0, (period_end - Time.current).to_i].max
|
|
283
289
|
end
|
|
@@ -312,8 +318,8 @@ module PricingPlans
|
|
|
312
318
|
when :blocked then 4
|
|
313
319
|
else 0
|
|
314
320
|
end,
|
|
315
|
-
message: (sev == :ok ? nil : message_for(
|
|
316
|
-
overage: overage_for(
|
|
321
|
+
message: (sev == :ok ? nil : ctx.message_for(limit_key)),
|
|
322
|
+
overage: ctx.overage_for(limit_key),
|
|
317
323
|
configured: true,
|
|
318
324
|
unlimited: unlimited,
|
|
319
325
|
remaining: remaining,
|
|
@@ -329,12 +335,12 @@ module PricingPlans
|
|
|
329
335
|
end
|
|
330
336
|
end
|
|
331
337
|
|
|
332
|
-
# Compute
|
|
338
|
+
# Compute overall helpers using context (all values already cached)
|
|
333
339
|
keys = items.map(&:key)
|
|
334
|
-
sev = highest_severity_for(
|
|
340
|
+
sev = ctx.highest_severity_for(*keys)
|
|
335
341
|
title = summary_title_for(sev)
|
|
336
|
-
msg =
|
|
337
|
-
highest_keys = keys.select { |k| severity_for(
|
|
342
|
+
msg = compute_summary_message_from_context(ctx, keys, sev)
|
|
343
|
+
highest_keys = keys.select { |k| ctx.severity_for(k) == sev }
|
|
338
344
|
highest_limits = items.select { |it| highest_keys.include?(it.key) }
|
|
339
345
|
human_keys = highest_keys.map { |k| k.to_s.humanize.downcase }
|
|
340
346
|
keys_sentence = if human_keys.respond_to?(:to_sentence)
|
|
@@ -648,5 +654,39 @@ module PricingPlans
|
|
|
648
654
|
def popular_plan_key
|
|
649
655
|
highlighted_plan_key
|
|
650
656
|
end
|
|
657
|
+
|
|
658
|
+
private
|
|
659
|
+
|
|
660
|
+
# Helper for status() that builds summary message using cached context data
|
|
661
|
+
def compute_summary_message_from_context(ctx, keys, sev)
|
|
662
|
+
return nil if keys.empty?
|
|
663
|
+
return nil if sev == :ok
|
|
664
|
+
|
|
665
|
+
affected = keys.select { |k| ctx.severity_for(k) == sev }
|
|
666
|
+
human_keys = affected.map { |k| k.to_s.humanize.downcase }
|
|
667
|
+
keys_list = if human_keys.respond_to?(:to_sentence)
|
|
668
|
+
human_keys.to_sentence
|
|
669
|
+
else
|
|
670
|
+
if human_keys.length <= 2
|
|
671
|
+
human_keys.join(" and ")
|
|
672
|
+
else
|
|
673
|
+
human_keys[0..-2].join(", ") + " and " + human_keys[-1]
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
noun = affected.size == 1 ? "plan limit" : "plan limits"
|
|
677
|
+
|
|
678
|
+
case sev
|
|
679
|
+
when :blocked
|
|
680
|
+
"Your #{noun} for #{keys_list} #{affected.size == 1 ? "has" : "have"} been exceeded. Please upgrade to continue."
|
|
681
|
+
when :grace
|
|
682
|
+
grace_end = keys.map { |k| ctx.grace_ends_at(k) }.compact.min
|
|
683
|
+
suffix = grace_end ? ", grace active until #{grace_end}" : ""
|
|
684
|
+
"You are over your #{noun} for #{keys_list}#{suffix}. Please upgrade to avoid service disruption."
|
|
685
|
+
when :at_limit
|
|
686
|
+
"You have reached your #{noun} for #{keys_list}."
|
|
687
|
+
else # :warning
|
|
688
|
+
"You are approaching your #{noun} for #{keys_list}."
|
|
689
|
+
end
|
|
690
|
+
end
|
|
651
691
|
end
|
|
652
692
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pricing_plans
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-02-16 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -61,8 +61,9 @@ executables: []
|
|
|
61
61
|
extensions: []
|
|
62
62
|
extra_rdoc_files: []
|
|
63
63
|
files:
|
|
64
|
-
- ".claude/settings.local.json"
|
|
65
64
|
- ".rubocop.yml"
|
|
65
|
+
- ".simplecov"
|
|
66
|
+
- AGENTS.md
|
|
66
67
|
- Appraisals
|
|
67
68
|
- CHANGELOG.md
|
|
68
69
|
- CLAUDE.md
|
|
@@ -86,6 +87,7 @@ files:
|
|
|
86
87
|
- lib/generators/pricing_plans/install/templates/initializer.rb
|
|
87
88
|
- lib/pricing_plans.rb
|
|
88
89
|
- lib/pricing_plans/association_limit_registry.rb
|
|
90
|
+
- lib/pricing_plans/callbacks.rb
|
|
89
91
|
- lib/pricing_plans/configuration.rb
|
|
90
92
|
- lib/pricing_plans/controller_guards.rb
|
|
91
93
|
- lib/pricing_plans/controller_rescues.rb
|
|
@@ -108,6 +110,7 @@ files:
|
|
|
108
110
|
- lib/pricing_plans/price_components.rb
|
|
109
111
|
- lib/pricing_plans/registry.rb
|
|
110
112
|
- lib/pricing_plans/result.rb
|
|
113
|
+
- lib/pricing_plans/status_context.rb
|
|
111
114
|
- lib/pricing_plans/version.rb
|
|
112
115
|
- lib/pricing_plans/view_helpers.rb
|
|
113
116
|
- sig/pricing_plans.rbs
|
data/.claude/settings.local.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(sed:*)",
|
|
5
|
-
"Bash(grep:*)",
|
|
6
|
-
"Bash(find:*)",
|
|
7
|
-
"mcp__context7__resolve-library-id",
|
|
8
|
-
"mcp__context7__get-library-docs",
|
|
9
|
-
"WebFetch(domain:github.com)",
|
|
10
|
-
"WebSearch",
|
|
11
|
-
"Bash(bundle exec rake test:*)",
|
|
12
|
-
"Bash(bundle install:*)",
|
|
13
|
-
"Bash(bundle exec appraisal:*)",
|
|
14
|
-
"Bash(ls:*)",
|
|
15
|
-
"Bash(done)"
|
|
16
|
-
],
|
|
17
|
-
"deny": []
|
|
18
|
-
}
|
|
19
|
-
}
|