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.
@@ -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
@@ -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).
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ module ApplicationHelper
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: 'from@example.com'
6
+ layout 'mailer'
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ class PlanFeature < ApplicationRecord
5
+ self.table_name = 'pay_features_plan_features'
6
+
7
+ belongs_to :plan
8
+ belongs_to :pay_feature, class_name: 'PayFeatures::PayFeature'
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Pay features</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "pay_features/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ PayFeatures::Engine.routes.draw do
4
+ end
@@ -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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddFieldsToPlan < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column PayFeatures.plan_table, :previous_plan_id, :integer
6
+ add_index PayFeatures.plan_table, :previous_plan_id
7
+ end
8
+ end
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace PayFeatures
6
+ end
7
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayFeatures
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :pay_features do
4
+ # # Task goes here
5
+ # 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: []