pay_features 0.1.0
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 +7 -0
- data/README.md +125 -0
- data/Rakefile +34 -0
- data/app/assets/config/pay_features_manifest.js +1 -0
- data/app/assets/stylesheets/pay_features/application.css +15 -0
- data/app/controllers/pay_features/application_controller.rb +7 -0
- data/app/helpers/pay_features/application_helper.rb +6 -0
- data/app/jobs/pay_features/application_job.rb +6 -0
- data/app/mailers/pay_features/application_mailer.rb +8 -0
- data/app/models/pay_features/application_record.rb +7 -0
- data/app/models/pay_features/pay_feature.rb +12 -0
- data/app/models/pay_features/plan_feature.rb +10 -0
- data/app/views/layouts/pay_features/application.html.erb +15 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20191230040540_create_pay_features_features.rb +16 -0
- data/db/migrate/20191230182348_create_pay_features_plan_features.rb +12 -0
- data/db/migrate/20191231065514_add_fields_to_plan.rb +8 -0
- data/lib/pay_features.rb +24 -0
- data/lib/pay_features/engine.rb +7 -0
- data/lib/pay_features/plan.rb +76 -0
- data/lib/pay_features/version.rb +5 -0
- data/lib/tasks/pay_features_tasks.rake +5 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4eab8434035887b300af20a8c5f228fd803d284badb50e867cb115e5d41ea83d
|
4
|
+
data.tar.gz: f4d4a7aa9b788f4d13fa19417d1a0c8007cae8ebbea50e46b5ef1461b824d98a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 242fc928e8e1d3195d8ae20ead69a859aae2bf7c8516c4845d5fdaf0a85804a61846c17d27f186377b115e3e5ab537a664fc1cb677b8a0c37c5e2ebc0358d323
|
7
|
+
data.tar.gz: 80bf87c6352c3701512e948f7fcef5a88d8580b002ec4d20529eb37528b411b9aef8f96947abb5add5768bd33054e6b449d9797488ff5872a7f69e6aa7df4daa
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# PayFeatures
|
2
|
+
|
3
|
+
PayFeatures is a gem to add `plan` features to your subscription plan. Let's say a user is subscribed to the Simple plan, which allows for 2 team_members. You can easily set up features and checks to use in your app and to display on your pricing page.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'pay_features'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install pay_features
|
23
|
+
```
|
24
|
+
|
25
|
+
## Migrations
|
26
|
+
|
27
|
+
Install migrations by running
|
28
|
+
|
29
|
+
```bash
|
30
|
+
rails pay_features:install:migrations
|
31
|
+
```
|
32
|
+
|
33
|
+
That will add two new tables as well as add `previous_plan_id` to your existing `plans` table.
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Per default we rely on an existing `plans` table to be present. This is only needed if you want the feature inheritance between plans.
|
38
|
+
|
39
|
+
In your existing `plan` model, add the following:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class Plan < ActiveModel
|
43
|
+
include PayFeatures::Plan
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
You can then set up features and your plans like this:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
first_plan = Plan.create(name: "First plan")
|
51
|
+
second_plan = Plan.create(name: "Second plan", previous_plan: first_plan)
|
52
|
+
|
53
|
+
first_plan.features.create(identifier: "users", amount: 2)
|
54
|
+
first_plan.features.create(identifier: "can_create_teams", description: "Can create teams")
|
55
|
+
second_plan.features.create(identifier: "users", amount: 10)
|
56
|
+
second_plan.features.create(identifier: "api_access", description: "Use the API")
|
57
|
+
```
|
58
|
+
|
59
|
+
You can perform simple checks like this:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
first_plan.has_feature?(:users)
|
63
|
+
# => true
|
64
|
+
|
65
|
+
first_plan.amount_for(:users)
|
66
|
+
# => 2
|
67
|
+
|
68
|
+
# Features are inherited when previous_plan is set
|
69
|
+
second_plan.has_feature?(:can_create_teams)
|
70
|
+
# => true
|
71
|
+
|
72
|
+
first_plan.has_feature?(:api_access)
|
73
|
+
# => false
|
74
|
+
|
75
|
+
second_plan.has_feature?(:api_access)
|
76
|
+
# => true
|
77
|
+
```
|
78
|
+
|
79
|
+
If you want to display the features on your pricing page, you can access `plan.display_features` which will give you an array similar to this:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
{
|
83
|
+
identifier: "users",
|
84
|
+
description: "2 users",
|
85
|
+
amount: 2,
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
If a feature exists on the previous plan, and you add it to the plan after similar to this:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
first_plan = Plan.create(name: "First plan")
|
93
|
+
second_plan = Plan.create(name: "Second plan", previous_plan: first_plan)
|
94
|
+
|
95
|
+
first_plan.features.create(identifier: "users", amount: 2, description: "2 users")
|
96
|
+
second_plan.features.create(identifier: "users", amount: 10, description: "10 users")
|
97
|
+
second_plan.features.create(identifier: "api_access", description: "API Access")
|
98
|
+
```
|
99
|
+
|
100
|
+
If you call `second_plan.display_features` you'll see the `users` feature marked as a new feature. This is so you can highlight differences in your pricing table.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
[
|
104
|
+
{
|
105
|
+
identifier: "users",
|
106
|
+
description: "10 users",
|
107
|
+
amount: 10,
|
108
|
+
new_feature: true,
|
109
|
+
},
|
110
|
+
{
|
111
|
+
identifier: "api_access",
|
112
|
+
description: "API Access",
|
113
|
+
amount: nil,
|
114
|
+
new_feature: true,
|
115
|
+
},
|
116
|
+
]
|
117
|
+
```
|
118
|
+
|
119
|
+
## Contributing
|
120
|
+
|
121
|
+
Open up Issue/PR if you encounter any problems or have ideas.
|
122
|
+
|
123
|
+
## License
|
124
|
+
|
125
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rdoc/task'
|
10
|
+
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
13
|
+
rdoc.title = 'PayFeatures'
|
14
|
+
rdoc.options << '--line-numbers'
|
15
|
+
rdoc.rdoc_files.include('README.md')
|
16
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
20
|
+
load 'rails/tasks/engine.rake'
|
21
|
+
|
22
|
+
load 'rails/tasks/statistics.rake'
|
23
|
+
|
24
|
+
require 'bundler/gem_tasks'
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
|
28
|
+
Rake::TestTask.new(:test) do |t|
|
29
|
+
t.libs << 'test'
|
30
|
+
t.pattern = 'test/**/*_test.rb'
|
31
|
+
t.verbose = false
|
32
|
+
end
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../stylesheets/pay_features .css
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PayFeatures
|
4
|
+
class PayFeature < ApplicationRecord
|
5
|
+
self.table_name = 'pay_features_pay_features'
|
6
|
+
|
7
|
+
has_many :plan_features, class_name: 'PayFeatures#PlanFeature'
|
8
|
+
has_many :plans, through: :plan_features
|
9
|
+
|
10
|
+
scope :ordered, -> { order(:order) }
|
11
|
+
end
|
12
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreatePayFeaturesFeatures < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
create_table :pay_features_pay_features do |t|
|
6
|
+
t.string :description
|
7
|
+
t.string :identifier
|
8
|
+
t.integer :amount
|
9
|
+
t.integer :order
|
10
|
+
t.boolean :hidden
|
11
|
+
t.boolean :enabled
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreatePayFeaturesPlanFeatures < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
create_table :pay_features_plan_features do |t|
|
6
|
+
t.references :plan, null: false, foreign_key: true
|
7
|
+
t.references :pay_feature, null: false, foreign_key: { to_table: :pay_features_pay_features }
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/pay_features.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pay_features/engine'
|
4
|
+
require 'pay_features/plan'
|
5
|
+
|
6
|
+
module PayFeatures
|
7
|
+
mattr_accessor :plan_class
|
8
|
+
mattr_accessor :plan_table
|
9
|
+
|
10
|
+
@@plan_class = 'Plan'
|
11
|
+
@@plan_table = 'plans'
|
12
|
+
|
13
|
+
def self.setup
|
14
|
+
yield self
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.plan_model
|
18
|
+
if Rails.application.config.cache_classes
|
19
|
+
@@plan_model ||= plan_class.constantize
|
20
|
+
else
|
21
|
+
plan_class.constantize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
5
|
+
module PayFeatures
|
6
|
+
module Plan
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
BIGGEST_AMOUNT = 9_999_999
|
10
|
+
SMALLEST_AMOUNT = -9_999_999
|
11
|
+
|
12
|
+
included do
|
13
|
+
has_many :plan_features, class_name: 'PayFeatures::PlanFeature'
|
14
|
+
has_many :pay_features, through: :plan_features, foreign_key: :pay_feature_id
|
15
|
+
|
16
|
+
belongs_to :previous_plan, class_name: PayFeatures.plan_class, optional: true
|
17
|
+
|
18
|
+
def amount_for(identifier)
|
19
|
+
feature = find_feature(identifier)
|
20
|
+
return SMALLEST_AMOUNT unless feature
|
21
|
+
return feature.amount if feature.amount
|
22
|
+
|
23
|
+
BIGGEST_AMOUNT
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_feature?(identifier)
|
27
|
+
find_feature(identifier).present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def display_features
|
31
|
+
display_features = features_from_previous
|
32
|
+
|
33
|
+
pay_features.ordered.each do |f|
|
34
|
+
index = display_features.index { |df| df[:identifier] == f.identifier }
|
35
|
+
value = { identifier: f.identifier, description: f.description, amount: f.amount }
|
36
|
+
|
37
|
+
if index
|
38
|
+
display_features[index] = value.merge(new_feature: true)
|
39
|
+
else
|
40
|
+
value.merge!(new_feature: true) if previous_plan
|
41
|
+
display_features << value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
display_features.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_feature(identifier)
|
49
|
+
feature = pay_features.find_by(identifier: identifier)
|
50
|
+
|
51
|
+
if feature
|
52
|
+
feature
|
53
|
+
elsif !feature && previous_plan.present?
|
54
|
+
previous_plan.find_feature(identifier)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def previous_plan_display_features
|
61
|
+
previous_plan.display_features
|
62
|
+
end
|
63
|
+
|
64
|
+
def features_from_previous
|
65
|
+
previous_features = []
|
66
|
+
return previous_features unless previous_plan
|
67
|
+
|
68
|
+
previous_plan_display_features.each do |f|
|
69
|
+
previous_features << { identifier: f[:identifier], description: f[:description] }
|
70
|
+
end
|
71
|
+
|
72
|
+
previous_features
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pay_features
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jesper Christiansen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-12-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 6.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Specify features for your subscription plans and check that the user
|
56
|
+
has access to those
|
57
|
+
email:
|
58
|
+
- hi@jespr.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- app/assets/config/pay_features_manifest.js
|
66
|
+
- app/assets/stylesheets/pay_features/application.css
|
67
|
+
- app/controllers/pay_features/application_controller.rb
|
68
|
+
- app/helpers/pay_features/application_helper.rb
|
69
|
+
- app/jobs/pay_features/application_job.rb
|
70
|
+
- app/mailers/pay_features/application_mailer.rb
|
71
|
+
- app/models/pay_features/application_record.rb
|
72
|
+
- app/models/pay_features/pay_feature.rb
|
73
|
+
- app/models/pay_features/plan_feature.rb
|
74
|
+
- app/views/layouts/pay_features/application.html.erb
|
75
|
+
- config/routes.rb
|
76
|
+
- db/migrate/20191230040540_create_pay_features_features.rb
|
77
|
+
- db/migrate/20191230182348_create_pay_features_plan_features.rb
|
78
|
+
- db/migrate/20191231065514_add_fields_to_plan.rb
|
79
|
+
- lib/pay_features.rb
|
80
|
+
- lib/pay_features/engine.rb
|
81
|
+
- lib/pay_features/plan.rb
|
82
|
+
- lib/pay_features/version.rb
|
83
|
+
- lib/tasks/pay_features_tasks.rake
|
84
|
+
homepage: https://github.com/jespr/pay_features
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubygems_version: 3.0.3
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Specify features for your subscription plans and check that the user has
|
107
|
+
access to those
|
108
|
+
test_files: []
|