rails_rbs 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 068d7378cafb40bcb836cd7858078ef5da1d6eb5
4
+ data.tar.gz: 7cf3527c974efefeee3c668d6793b43a9bbf680e
5
+ SHA512:
6
+ metadata.gz: 882d6a91dedbbfc79ed14db7b16cf47ae92248927f72e04990daf436020f5c7799b0b298e0f153a13b5929d6e1f899b5fff992e722936556cd8eeb98712b456d
7
+ data.tar.gz: e1bc6b497233a18ac43239c95fe3dd7db9d99940f8c876310d3c230c06171bfce2ab8b2a46784999668bc9d653853fa61fac2777a516327f9c138de8f6712a0d
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Rob
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = RailsRbs
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'RailsRbs'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsRbs
4
+ # This controller class is the C.R.U.D. interface for rule-sets. Whenever the RailsRbs configuration
5
+ # uses rule_owners (@see RailsRbs Config) rule_sets will only be displayed and modified that belong
6
+ # to that owner. This controller will attempt to render views under a rule_sets/ directory if view
7
+ # rendering is enabled (again @see RailsRbs Config)
8
+ #
9
+ # @note When locating a rule_set by ID this controller will use the config setting - request_id_field
10
+ # like all other configuration @see RailsRbs Config
11
+ class RuleSetsController < RailsRbs.parent_controller.constantize
12
+ include RailsRbs::Controllers::Helpers
13
+ # If owner access is enabled validate that an owner is present
14
+ before_action :validate_owner_access
15
+ before_action :create_rule_set, only: :create
16
+ before_action :load_rule_set, only: [:show, :update, :destroy]
17
+
18
+ # #index displays all rule_sets either for the owner or from the entire table if owner_access is
19
+ # disabled (@see RailsRbs Config). The rule_sets will always include any associated rules.
20
+ def index
21
+ if RailsRbs.owner_access
22
+ @rule_sets = rule_owner.rule_sets.includes(:rules, :rule_actions)
23
+ else
24
+ @rule_sets = RuleSet.all.includes(:rules, :rule_actions)
25
+ end
26
+ render_view_fallback('rule_sets/index') do
27
+ render json: @rule_sets
28
+ end
29
+ end
30
+ # #show displays a single rule_set depending on the provided id. The method uses a
31
+ # protected load method that will raise an ActiveRecord::RecordNotFound error if a rule_set is
32
+ # not located. This includes providing an existing ID for a rule_set not owned by the
33
+ # requesting owner (only if owner access is enabled - @see RailsRbs Config).
34
+ def show
35
+ render_view_fallback('rule_sets/show') do
36
+ render json: @rule_set
37
+ end
38
+ end
39
+ # #update updates a single rule_set, including any associated rules by first using the
40
+ # protected load_rule_set method (@see #show). If an update fails the action will raise
41
+ # an ActiveRecord::RecordInvalid error. Similar to #show this method will only operate on
42
+ # rule_sets that belong to the rule_set owner (if owner access is enabled).
43
+ def update
44
+ update_with_nested(key: :rules_attributes, klass: Rule)
45
+ update_with_nested(key: :rule_actions_attributes, klass: RuleAction)
46
+ @rule_set.update!(rule_set_params) unless rule_set_params.empty? # This can be empty after update_with_nested
47
+ # Force a reload in case the above update never ran
48
+ @rule_set.reload
49
+ render_view_fallback('rule_sets/show') do
50
+ render json: @rule_set
51
+ end
52
+ end
53
+ # #create will attempt to create a new rule_set and like all other actions in this
54
+ # controller it will do so for the requesting owner if owner access is enabled
55
+ # (@see RailsRbs Config). If owner access is enabled but no requesting owner can be found
56
+ # an ActiveRecord::RecordNotFound error will be raised.
57
+ def create
58
+ render_view_fallback('rule_sets/show') do
59
+ render json: @rule_set
60
+ end
61
+ end
62
+ # #destroy will attempt to destroy a rule_set loading it using the same protected load rule_set
63
+ # used by #show and #update. If destroy causes any issues an ActiveRecord error will be raised,
64
+ # otherwise the action renders the index view or nothing at all if view rendering is disabled
65
+ # (@see RailsRbs Config)
66
+ def destroy
67
+ @rule_set.destroy!
68
+ render_view_fallback('rule_sets/index') do
69
+ render nothing: true, status: 200
70
+ end
71
+ end
72
+
73
+ protected
74
+ # #load_rule_set is meant to be used as a before action to ensure the appropriate resource is
75
+ # located for whatever crud operation is interested. In this case the method will raise an
76
+ # ActiveRecord::RecordNotFound error when no record is found.
77
+ def load_rule_set
78
+ if RailsRbs.owner_access
79
+ rule_sets_association = rule_owner.rule_sets
80
+ else
81
+ rule_sets_association = RuleSet
82
+ end
83
+ @rule_set = rule_sets_association.includes(:rules, :rule_actions).find_by!(RailsRbs.request_id_field => params[:id])
84
+ end
85
+
86
+ # create_rule_set is meant to be used as a before action that needs to instantiate a new
87
+ # rule_set resource. This method like all others operates on the rule_owners rule_sets if
88
+ # owner access is enabled.
89
+ def create_rule_set
90
+ if RailsRbs.owner_access
91
+ @rule_set = rule_owner.rule_sets.create!(rule_set_params)
92
+ else
93
+ @rule_set = RuleSet.create!(rule_set_params)
94
+ end
95
+ raise ActiveRecord::InvalidRecord.new(@rule_set) if @rule_set.invalid?
96
+ @rule_set
97
+ end
98
+
99
+ def rule_set_params
100
+ return @rule_set_params if @rule_set_params.present?
101
+ rule_fields = RailsRbs.base_rule_fields.concat(RailsRbs.rule_fields).concat([:id, :_destroy]).uniq.reject(&:nil?)
102
+ rule_set_fields = [:scope, {rules_attributes: rule_fields, rule_actions_attributes: [RailsRbs.request_id_field.to_sym, :id, :_destroy, :enforced_value, :enforced_field]}].reject(&:nil?)
103
+ rule_set_fields << "#{RailsRbs.owned_by}_id".to_sym if RailsRbs.owner_access
104
+ @rule_set_params = params.require(RailsRbs.rule_set_params_root.to_sym).permit(rule_set_fields)
105
+ end
106
+
107
+ def validate_owner_access
108
+ if RailsRbs.owner_access && rule_owner.nil?
109
+ raise ActiveRecord::RecordNotFound.new("Unable to find owner required to create rule_set")
110
+ end
111
+ end
112
+
113
+ def update_with_nested(key: ,klass:)
114
+ # If using id as the rule set identifier let accepts_nested_attributes do it's job
115
+ if RailsRbs.request_id_field.to_sym != :id && rule_set_params[key].present?
116
+ id_field = RailsRbs.request_id_field.to_sym
117
+ updates, deletes, creates = [], [], []
118
+ rule_set_params[key].each do |attrs|
119
+ attrs[:rule_set_id] ||= @rule_set.try(:id)
120
+ if attrs[:id].present?
121
+ if attrs[:_destroy]
122
+ deletes << attrs
123
+ else
124
+ updates << attrs
125
+ end
126
+ else
127
+ creates << attrs
128
+ end
129
+ end
130
+ updates.each { |attrs| klass.find_by!(id_field => attrs.delete('id')).update(attrs) }
131
+ klass.where(id_field => deletes.map { |attrs| attrs['id'] }).delete_all
132
+ creates.each { |attrs| klass.create!(attrs) }
133
+ rule_set_params[key] = {}
134
+ end
135
+ end
136
+
137
+ ActiveSupport.run_load_hooks(:rule_sets_controller, self)
138
+ end
139
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsRbs
4
+ # This controller class is the C.R.UD. interface for rules. Whenever the RailsRbs configuration
5
+ # uses rule_owners (@see RailsRbs Config) rules will only be displayed and modified that belong
6
+ # to that owner. This controller will attempt to render views under a rules/ directory if view
7
+ # rendering is enabled ( again @see RailsRbs Config)
8
+ #
9
+ # @note when locationg a rule by ID this contoller will use the config settings - request_id_field
10
+ # like all other configuations @see RailsRbs Config
11
+ class RulesController < RailsRbs.parent_controller.constantize
12
+ include RailsRbs::Controllers::Helpers
13
+ # If owner access is enabled validate that an owner is present
14
+ before_action :validate_owner_access
15
+ before_action :load_rule, only: [:show, :update, :destroy]
16
+ before_action :create_rule, only: :create
17
+
18
+ # #index displays all rules either for the owner or from the entire table if owner_access is
19
+ # disabled (@see RailsRbs Config).
20
+ def index
21
+ if RailsRbs.owner_access
22
+ @rules = rule_owner.try(:rules)
23
+ else
24
+ @rules = Rule.all
25
+ end
26
+ render_view_fallback('rules/index') do
27
+ render json: @rules
28
+ end
29
+ end
30
+ # #show displays a single rule depending on the provided id. The method uses a protected
31
+ # load method that will rails an ActiveSupport::RecordNotFound error if the rule is not
32
+ # located. This includes providing an ID for an existing rule not owned by the requesting
33
+ # owner (if owner access is enabled - @see RailsRbs Config).
34
+ def show
35
+ render_view_fallback('rules/show') do
36
+ render json: @rule
37
+ end
38
+ end
39
+ #update updates a single rule by first using a protected load method to locate the rule
40
+ # (@see #show). If an update fails the action will rails an ActiveRecord::RecordInvalid
41
+ # error. Similar to #show this method will only operate on rules that belong to the rule
42
+ # owner if owner access is enabled.
43
+ def update
44
+ @rule.update!(rule_params)
45
+ render_view_fallback('rules/show') do
46
+ render json: @rule
47
+ end
48
+ end
49
+ # #create will attempt to create a new rule and like the other actions in this controller
50
+ # it will do so for the requesting owner if owner access is enabled (@see RailsRbs Config).
51
+ # If owner_access is enabled but no requesting owner can be found an
52
+ # ActiveRecord::RecordNotFound error will be raised.
53
+ def create
54
+ render_view_fallback('rules/show') do
55
+ render json: @rule
56
+ end
57
+ end
58
+ # #destroy will attempt to destroy a rule loading it using the same protected load rule method
59
+ # used by #show and #update. If destroy causes any issues an ActiveRecord error will be raised,
60
+ # otherwise the action renders the index view or nothing at lal if view rendering is disabled
61
+ # (@see RailsRbs Config).
62
+ def destroy
63
+ @rule.destroy!
64
+ render_view_fallback('rules/index') do
65
+ render nothing: true, status: 200
66
+ end
67
+ end
68
+
69
+ protected
70
+ # #load_rule is meant to be used as a before action to ensure the appropriate resource is
71
+ # located for whatever crud operation is interested. In this case the method will raise an
72
+ # ActiveRecord::RecordNotFound error when no record is found.
73
+ def load_rule
74
+ if RailsRbs.owner_access
75
+ rules_association = rule_owner.rules
76
+ else
77
+ rules_association = Rule
78
+ end
79
+ @rule = rules_association.find_by!(RailsRbs.request_id_field => params[:id])
80
+ end
81
+
82
+ # Attempt to fetch the rule_set for the provided parameters and fall back to any rule object
83
+ # that may be in context for this request.
84
+ def rule_set
85
+ if RailsRbs.owner_access
86
+ @rule_set ||= rule_owner.rule_sets.find_by!(id: rule_params[:rule_set_id])
87
+ else
88
+ @rule_set ||= RuleSet.find_by!(id: rule_params[:rule_set_id])
89
+ end
90
+ @rule_set ||= @rule.try(:rule_set)
91
+ end
92
+
93
+ def create_rule
94
+ if RailsRbs.owner_access
95
+ @rule = rule_owner.rules.create!(rule_params)
96
+ else
97
+ @rule = Rule.create!(rule_params)
98
+ end
99
+ end
100
+
101
+ def rule_params
102
+ rule_fields = RailsRbs.base_rule_fields.concat(RailsRbs.rule_fields).uniq.reject(&:nil?)
103
+ params.require(RailsRbs.rule_params_root.to_sym).permit(rule_fields)
104
+ end
105
+
106
+ def validate_owner_access
107
+ if RailsRbs.owner_access && rule_owner.nil?
108
+ raise ActiveRecord::RecordNotFound.new("Unable to find owner required to create rule")
109
+ end
110
+ end
111
+
112
+ ActiveSupport.run_load_hooks(:rules_controller, self)
113
+ end
114
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsRbs
4
+ # This module of helpers is utilized by the RailsRbs generators to detect model and migration files
5
+ # at their correct path
6
+ module MigrationHelpers
7
+
8
+ # Detect if the rules and rule_sets table migrations exist.
9
+ # @return true if the rules and rule_sets migration files exist
10
+ def rule_migrations_exist?
11
+ %w[add_rules add_rule_sets add_rule_actions].any? do |name|
12
+ Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb").grep(/\d+_#{name}.rb$/).first
13
+ end
14
+ end
15
+
16
+ # Detect if the migrations for the default rule types exist.
17
+ # @return true if date_range and location rule migration files exist
18
+ def default_rule_migrations_exist?
19
+ %w[add_date_range add_location].all? do |name|
20
+ Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb").grep(/\d+_#{name}_rule.rb$/).first
21
+ end
22
+ end
23
+
24
+ # Detect if a specific model exists by singular name
25
+ # @return true if the a file is found in app/models that matches the name provided
26
+ def model_exists?(name: )
27
+ File.exists?(File.join(destination_root, model_path(name: name)))
28
+ end
29
+
30
+ # Return the file path for a model using the name as the singular file name
31
+ # @return [String] The relative path of the model file (app/models/rule.rb for name: rule)
32
+ def model_path(name: )
33
+ File.join("app", "models", "#{name}.rb")
34
+ end
35
+
36
+ # @return [String] The relative path to the directory where migration files are stored
37
+ def migration_path
38
+ if self.respond_to?(:db_migrate_path) # Rails 5.0.3 and up
39
+ db_migrate_path
40
+ else
41
+ File.join("db", "migrate")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_record/migration'
4
+ require 'generators/rails_rbs/migration_helpers'
5
+ require 'active_support/all'
6
+
7
+ module RailsRbs
8
+ module Generators
9
+ class RailsRbsGenerator < Rails::Generators::Base
10
+ include ActiveRecord::Generators::Migration
11
+ include RailsRbs::MigrationHelpers
12
+
13
+ namespace "rails_rbs"
14
+ desc "Generates the rule and rule set models as well as their migrations. This " \
15
+ "generator will also create the default rule models and crud routes, both can be skipped"
16
+
17
+ class_option :skip_defaults, type: :boolean, default: false, desc: "Skip the default rule models being generated"
18
+ class_option :skip_routes, desc: "Generate routes", type: :boolean, default: false
19
+ class_option :owned_by, desc: "What entity owns or creates the rule", type: :string, default: 'user'
20
+
21
+ source_root File.expand_path("../templates", __FILE__)
22
+
23
+ def copy_migrations
24
+ return if rule_migrations_exist?
25
+ migration_template 'rule_migration.rb', "#{migration_path}/add_rules.rb"
26
+ migration_template 'rule_set_migration.rb', "#{migration_path}/add_rule_sets.rb"
27
+ migration_template 'rule_action_migration.rb', "#{migration_path}/add_rule_actions.rb"
28
+ end
29
+
30
+ def generate_models
31
+ generate "active_record:model rule --no-migration" unless model_exists?(name: 'rule')
32
+ generate "active_record:model rule_set --no-migration" unless model_exists?(name: 'rule_set')
33
+ generate "active_record:model rule_action --no-migration" unless model_exists?(name: 'rule_action')
34
+ end
35
+
36
+ def inject_model_content
37
+ inject_into_class(model_path(name: 'rule'), 'Rule', rule_model_content)
38
+ inject_into_class(model_path(name: 'rule_set'), 'RuleSet', rule_set_model_content)
39
+ inject_into_class(model_path(name: 'rule_action'), 'RuleAction', rule_action_model_content)
40
+ if options.owned_by && model_exists?(name: options.owned_by)
41
+ inject_into_class(model_path(name: options.owned_by), options.owned_by.to_s.camelcase, owner_model_content)
42
+ end
43
+ end
44
+
45
+ def generate_default_rule_models
46
+ generate 'rails_rbs:rule equality' unless model_exists?(name: 'equality_rule')
47
+ generate 'rails_rbs:rule date_range' unless model_exists?(name: 'date_range_rule')
48
+ generate 'rails_rbs:rule location' unless model_exists?(name: 'location_rule')
49
+ end
50
+
51
+ def add_rbs_routes
52
+ return if options.skip_routes
53
+ %i[equality date_time location].each do |rule_name|
54
+ new_route = "resources :#{rule_name}_rules, controller: 'rails_rbs/rules', only: [:create, :update, :show, :destroy]"
55
+ new_route = new_route + ' skip: :all' if options.skip_defaults
56
+ route new_route
57
+ end
58
+ route 'resources :rules, controller: "rails_rbs/rules", only: [:index, :create, :update, :show, :destroy]'
59
+ route 'resources :rule_sets, controller: "rails_rbs/rule_sets", only: [:index, :create, :update, :show, :destroy]'
60
+ route "# RBS Generated routes"
61
+ end
62
+
63
+ def generate_initializer
64
+ template "initializer.rb", "config/initializers/rails_rbs.rb"
65
+ end
66
+
67
+ private
68
+ def rule_model_content
69
+ <<-CONTENT
70
+ include RailsRbs::FollowsRule
71
+ CONTENT
72
+ end
73
+
74
+ def rule_set_model_content
75
+ <<-CONTENT
76
+ include RailsRbs::FollowsRuleSet
77
+ CONTENT
78
+ end
79
+
80
+ def owner_model_content
81
+ <<-CONTENT
82
+ # RailsRbs rule associations
83
+ has_many :rules, dependent: :destroy
84
+ has_many :rule_sets, dependent: :destroy
85
+ CONTENT
86
+ end
87
+
88
+ def rule_action_model_content
89
+ <<-CONTENT
90
+ include RailsRbs::RunsAction
91
+ CONTENT
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_record'
4
+ require 'generators/rails_rbs/migration_helpers'
5
+ require 'active_support/all'
6
+
7
+ module RailsRbs
8
+ module Generators
9
+ class RuleGenerator < Rails::Generators::NamedBase
10
+ include ActiveRecord::Generators::Migration
11
+ include RailsRbs::MigrationHelpers
12
+
13
+ namespace 'rails_rbs:rule'
14
+
15
+ source_root(File.expand_path('../templates', __FILE__))
16
+
17
+ def generate_rule_model
18
+ invoke("active_record:model", [model_name], migration: false, parent: 'Rule') unless model_exists?(name: model_name)
19
+ end
20
+
21
+ def generate_migrations
22
+ unless default_rule_migrations_exist?
23
+ if name == 'date_range'
24
+ migration_template 'date_range_rule_migration.rb', "#{migration_path}/add_date_range_rule.rb"
25
+ elsif name == 'location'
26
+ migration_template 'location_rule_migration.rb', "#{migration_path}/add_location_rule.rb"
27
+ end
28
+ end
29
+ end
30
+
31
+ def inject_rule
32
+ default_rules = %w[equality date_range location] # The built in rules of the gem
33
+ if default_rules.include?(name)
34
+ content = default_rule_content
35
+ else
36
+ content = empty_rule_content
37
+ end
38
+ inject_into_class(model_path(name: model_name), "#{name.to_s.camelize}Rule", content)
39
+ end
40
+
41
+ private
42
+ def empty_rule_content
43
+ <<-CONTENT
44
+ def follows_rule?(*objects)
45
+ # Your rule logic here!
46
+ false
47
+ end
48
+ CONTENT
49
+ end
50
+
51
+ def default_rule_content
52
+ <<-CONTENT
53
+ default_rule :#{name}
54
+ CONTENT
55
+ end
56
+
57
+ def model_name
58
+ "#{name}_rule"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ class AddDateRangeRule < ActiveRecord::Migration
3
+ def change
4
+ change_table :rules do |table|
5
+ table.datetime :start_date
6
+ table.datetime :end_date
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ RailsRbs.setup do |config|
2
+ # Configure RailsRBS here
3
+ config.owned_by = :<%= options.owned_by %>
4
+ config.owner_access = :<%= "current_#{options.owned_by}" %>
5
+ config.render_views = true
6
+ config.request_id_field = :id
7
+ config.parent_controller = 'ApplicationController'
8
+ config.rule_params_root = :rule
9
+ config.rule_set_params_root = :rule_set
10
+ config.rule_set_view_dir = 'rule_sets'
11
+ config.rule_view_dir = 'rules'
12
+ =begin
13
+ * Specialized rule fields *
14
+ The following list is used to support strong parameters for rules that leverage single table inheritance
15
+ - The default strong parameters method uses this set of allowed parameters
16
+ - This list can be expanded or specialized for different rule types
17
+ - When a specialized rule requires a new table column, add that field to this list to ensure access at the
18
+ controller level
19
+ - Specialized controllers can utilize or ignore this list of fields
20
+ - Some fields are required by the rule system and should always be allowed, these will never be listed in the
21
+ rule_fields list and can be accessed at RailsRbs.base_rule_fields
22
+ - Fields always included in default controllers - [:type, :enforced_type, :enforced_value, :observed_field,
23
+ :<owned_by>_id]
24
+ - The <owned_by>_id field is always added in the default strong parameters method
25
+ =end
26
+ config.rule_fields = %i[value start_date end_date longitude latitude]
27
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ class AddLocationRule < ActiveRecord::Migration
3
+ def change
4
+ change_table :rules do |table|
5
+ table.float :latitude
6
+ table.float :longitude
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ class AddRuleActions < ActiveRecord::Migration
3
+ def change
4
+ create_table :rule_actions do |table|
5
+ table.integer :rule_set_id, null: false, index: true
6
+ table.string :enforced_field, null: false
7
+ table.string :enforced_value
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ class AddRules < ActiveRecord::Migration
3
+ def change
4
+ create_table :rules do |table|
5
+ table.string :type, null: false
6
+ table.string :enforced_type, null: false, default: :string
7
+ table.string :enforced_value
8
+ table.string :observed_field, null: false
9
+ table.string :comparison_operator, null: false, default: '='
10
+ table.integer :<%= options.owned_by %>_id, index: true
11
+ table.integer :rule_set_id, null: false, index: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ class AddRuleSets < ActiveRecord::Migration
3
+ def change
4
+ create_table :rule_sets do |table|
5
+ table.integer :<%= options.owned_by %>_id, index: true
6
+ table.string :scope, index: true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module RailsRbs
6
+ autoload :FollowsRule, 'rails_rbs/follows_rule'
7
+ autoload :FollowsRuleSet, 'rails_rbs/follows_rule_set'
8
+ autoload :RunsAction, 'rails_rbs/runs_action'
9
+
10
+ module Rules
11
+ autoload :Equality, 'rails_rbs/rules/equality'
12
+ autoload :DateRange, 'rails_rbs/rules/date_range'
13
+ autoload :Location, 'rails_rbs/rules/location'
14
+ end
15
+
16
+ module Controllers
17
+ autoload :Helpers, 'rails_rbs/controllers/helpers'
18
+ end
19
+
20
+ module Models
21
+ autoload :FollowsScope, 'rails_rbs/models/follows_scope'
22
+ end
23
+
24
+ # Default way to set up RailsRbs. Run rails generate rails_rbs to create
25
+ # a fresh initializer with all configuration values.
26
+ def self.setup
27
+ yield self
28
+ end
29
+
30
+ mattr_accessor :owned_by
31
+ @@owned_by = :user
32
+
33
+ mattr_accessor :owner_access
34
+ @@owner_access = :current_user
35
+
36
+ mattr_accessor :render_views
37
+ @@render_views = true
38
+
39
+ mattr_accessor :request_id_field
40
+ @@request_id_field = :id
41
+
42
+ mattr_accessor :base_rule_fields
43
+ @@base_rule_fields = %i[type enforced_type enforced_value observed_field rule_set_id comparison_operator]
44
+
45
+ mattr_accessor :rule_fields
46
+ @@rule_fields = %i[value start_date end_date longitude latitude]
47
+
48
+ mattr_accessor :parent_controller
49
+ @@parent_controller = 'ApplicationController'
50
+
51
+ mattr_accessor :rule_params_root
52
+ @@rule_params_root = :rule
53
+
54
+ mattr_accessor :rule_set_params_root
55
+ @@rule_set_params_root = :rule_set
56
+
57
+ mattr_accessor :rule_set_view_dir
58
+ @@rule_set_view_dir = 'rule_sets'
59
+
60
+ mattr_accessor :rule_view_dir
61
+ @@rule_view_dir = 'rules'
62
+ end
63
+
64
+ require 'rails_rbs/engine'
@@ -0,0 +1,33 @@
1
+ require 'rails'
2
+
3
+ module RailsRbs
4
+ module Controllers
5
+ # Helpers that are used across RailsRbs default controllers
6
+ module Helpers
7
+ # Attempts to call the owner_access method stored by the RailsRbs configuration, only
8
+ # if owner_access is not falsey. @see RailsRbs Config
9
+ # @return Rule/RuleSet owner if owner_access is not false
10
+ def rule_owner
11
+ self.send(RailsRbs.owner_access.to_sym) if RailsRbs.owner_access
12
+ end
13
+ # Based on the RailsRbs configuration returns the id field of the owner. This is
14
+ # simply derived using the owned_by configuration (@see RailsRbs Config).
15
+ # @return [String] name of the owner id field used in rules and rule_sets (e.g. user_id)
16
+ def owner_id_field
17
+ "#{RailsRbs.owned_by.to_s.camelize}_id"
18
+ end
19
+ # Helper method that will render the view_path provided only if render_views is enabled
20
+ # (@see RailsRbs Config). If this configuration setting is false then simply yield the
21
+ # provided fallback block.
22
+ # @param view_path [String] the path to the view to be rendered
23
+ # @param fallback A block to yield if render_views is disabled
24
+ def render_view_fallback(view_path, &fallback)
25
+ if RailsRbs.render_views
26
+ render view_path.gsub(/\Arule_sets/, RailsRbs.rule_set_view_dir).gsub(/\Arules/, RailsRbs.rule_view_dir)
27
+ else
28
+ yield fallback
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module Rbs
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module RailsRbs
6
+ module FollowsRule
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ # RailsRBS Associations
11
+ belongs_to :rule_set
12
+ # Simply for easier owner-rule look-ups
13
+ belongs_to "#{RailsRbs.owned_by.to_s.underscore}".to_sym
14
+ # Require enforced type and field, as well as STI type
15
+ validates_presence_of :type
16
+ validates_presence_of :enforced_type
17
+ validates_presence_of :observed_field
18
+ validates_inclusion_of :comparison_operator, in: %w(= != =~ > >= < <=)
19
+
20
+ before_create :default_owner if RailsRbs.owner_access
21
+ end
22
+
23
+ # Defined for documentation, and prevents user from not defining a rule class correctly somehow...
24
+ def follows_rule?(*objects)
25
+ raise NotImplementedError.new("This instance of #{self.class.to_s} fails to define a follows_rule? method though it claims to be a rule...")
26
+ end
27
+ def filter_objects(association)
28
+ # association.where for example
29
+ raise NotImplementedError.new("This instance of #{self.class.to_s} fails to define a find_objects method though it claims to be a rule...")
30
+ end
31
+ # Coerce the provided object to the rule's enforced_type. If the enforced type is a string
32
+ # integer or float then to_s, to_i or to_f are used, otherwise we attempt to call
33
+ # to_<enforced_type> on the provided object.
34
+ # @param object The object to coerce
35
+ def force_type(object)
36
+ return unless self.respond_to?(:enforced_type)
37
+ enforced_type_string = self.enforced_type.to_s.downcase
38
+ if enforced_type_string.to_s.downcase.to_sym == :string
39
+ object.to_s
40
+ elsif enforced_type_string.to_s.downcase =~ /\Aint(?!iger)\z/
41
+ object.to_i
42
+ elsif enforced_type_string.to_s.downcase.to_sym == :float
43
+ object.to_f
44
+ else
45
+ object.send("to_#{enforced_type_string}")
46
+ end
47
+ end
48
+ def default_owner
49
+ owner_field = "#{RailsRbs.owned_by.to_s.underscore}".to_sym
50
+ owned_by = self.send(owner_field)
51
+ self.send("#{owner_field}=", self.rule_set.send(owner_field)) if owned_by.nil? && self.rule_set.present?
52
+ end
53
+
54
+ class_methods do
55
+ def supported_defaults
56
+ %i[equality date_range location]
57
+ end
58
+ def default_rule(type)
59
+ return unless supported_defaults.include?(type)
60
+ class_eval { include("RailsRbs::Rules::#{type.to_s.camelize}".constantize) }
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module RailsRbs
6
+ module FollowsRuleSet
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ # Container of rules and actions that are destroyed on set destruction
11
+ has_many :rules, dependent: :destroy
12
+ has_many :rule_actions, dependent: :destroy
13
+ # Belongs to the owned_by configuration value unless it is nil
14
+ belongs_to "#{RailsRbs.owned_by.to_s.underscore}".to_sym unless RailsRbs.owned_by.nil?
15
+ # Allow API CRUD of nested rules and actions
16
+ accepts_nested_attributes_for :rules, allow_destroy: true
17
+ accepts_nested_attributes_for :rule_actions, allow_destroy: true
18
+ scope :for, -> (scope) { where(scope: scope) }
19
+ end
20
+ # Check if an object or collection of objects follows all rules in this rule_set
21
+ # @param objects [Array<ActiveRecord::Base> | ActiveRecord::Base] a single or collection of active
22
+ # record objects to check this rule_set against.
23
+ # @return true if the object or objects follows all rules in the rule_set
24
+ def follows_rule_set?(*objects)
25
+ self.rules.all? { |rule| rule.follows_rule?(*objects.flatten) }
26
+ end
27
+ # Apply the rule_set to all matching objects in the provided collection. Application simply means
28
+ # the enforced_field will be set to the enforced_value for any objects that follow all rules
29
+ # in the set.
30
+ # @param objects [Array<ActiveRecord::BAse> | ActiveRecord::Base] a single collection of active
31
+ # record objects to check this apply this rule_set to. Only objects that follow all rules in the
32
+ # set will have the rule_set applied.
33
+ def apply_rule_set(*objects)
34
+ follows_rules = filter_objects(objects)
35
+ return if follows_rules.empty?
36
+ self.rule_actions.map { |rule_action| rule_action.apply(follows_rules.flatten) }
37
+ end
38
+ # Return a collection of active record objects that follow all rules in the rule_set using the
39
+ # provided collection or association as a base to query from.
40
+ # @param objects collection of objects or an association object that supports the where interface
41
+ # @return Array collection of objects that follow all rules in the rule_set
42
+ def filter_objects(*objects)
43
+ if objects.count == 1 && objects.first.respond_to?(:where)
44
+ follows_rules = objects.first
45
+ self.rules.each { |rule| follows_rules = rule.filter_objects(follows_rules) }
46
+ else
47
+ follows_rules = objects.select { |obj| self.follows_rule_set?(obj) }
48
+ end
49
+ follows_rules
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/array'
5
+
6
+ module RailsRbs
7
+ module Models
8
+ # This model concern adds convenience methods to active record objects that utilize
9
+ # rule_sets and rules. This concern is included in the ActiveRecord::Base object.
10
+ module FollowsScope
11
+ extend ActiveSupport::Concern
12
+
13
+ # Check if a model instance follows a rule or rule_set. When using a rule_set
14
+ # the model must pass every rule in the set.
15
+ # @param rule_or_set [Rule | RuleSet] The rule or rule_set to check this instance
16
+ # follows.
17
+ # @return true if this model instance follows the provided rule or all rules in
18
+ # the provided rule_set
19
+ def follows?(rule_or_set)
20
+ if rule_or_set.is_a?(RuleSet)
21
+ rule_or_set.follows_rule_set?(self)
22
+ else
23
+ rule_or_set.follows_rule?(self)
24
+ end
25
+ end
26
+ # Apply the provided rule_set to the current model instance. The rule_set will
27
+ # only be applied if the the instance follows all the rules in associated with
28
+ # the set.
29
+ # @param rule_set [RuleSet] The rule_set to check and apply the instance to
30
+ def apply_rule_set(rule_set)
31
+ rule_set.apply_rule_set(self)
32
+ end
33
+
34
+ class_methods do
35
+ # Query the active record class for objects that follow the rule(s) or set(s)
36
+ # provided.
37
+ # @param rules_or_sets [Array<Rule> | Array<RuleSet> | Rule | Set] The rule(s)
38
+ # or rule_set(s) to query objects with.
39
+ # @return Array<ActiveRecord::Object> Collection of active record objects that
40
+ # follow the rule(s) or set(s)
41
+ def following(*rules_or_sets)
42
+ rules_or_sets.reduce(self) { |followers, rule_or_set| rule_or_set.filter_objects(followers) }
43
+ end
44
+ # Apply the provided rule_set to any objects that follow the rule(s) or set(s)
45
+ # provided. This allows users to provide rule_sets to existing data.
46
+ # @param rule_set [RuleSet] The rule_set to query with and apply to resulting
47
+ # objects.
48
+ def apply_rule_set(rule_set)
49
+ rule_set.apply_rule_set(self)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ ActiveRecord::Base.send(:include, Models::FollowsScope)
55
+ end
@@ -0,0 +1,41 @@
1
+ module RailsRbs
2
+ module Rules
3
+ # Used by the date_range default rule type to ensure an object's observed_field
4
+ # falls in a certain range of two dates. Assumes the rule model supports a
5
+ # start_date and end_date field
6
+ module DateRange
7
+ # Check one or more object's observed_field to ensure it falls between a
8
+ # start_date and end_date
9
+ # @param objects [Array<ActiveRecord::Base>] active record objects, or objects
10
+ # that respond to the observed_field value of the including rule.
11
+ # @return true if the provided object(s) follow the current rule
12
+ def follows_rule?(*objects)
13
+ objects.all? do |object|
14
+ source_date = object.send(self.observed_field.to_sym)
15
+ range_start, range_end = self.start_date, self.end_date
16
+ # Attempt to coerce the types if we can
17
+ if self.respond_to?(:enforced_type) && self.respond_to?(:force_type)
18
+ source_date = force_type(source_date)
19
+ range_start = force_type(range_start)
20
+ range_end = force_type(range_end)
21
+ end
22
+ # Was having mixed results with .between method
23
+ source_date >= range_start && source_date <= range_end
24
+ end
25
+ end
26
+ # Return an association of active record objects that fall in the date range
27
+ # of the rule using the provided association as a base to query from.
28
+ # @param association Active record association, relation or collection proxy
29
+ # that supports the where interface.
30
+ def filter_objects(association)
31
+ start_date, end_date = self.start_date, self.end_date
32
+ # Attempt to coerce the types if we can
33
+ if self.respond_to?(:enforced_type) && self.respond_to?(:force_type)
34
+ start_date = force_type(start_date)
35
+ end_date = force_type(end_date)
36
+ end
37
+ association.where("#{self.observed_field} >= ? AND #{self.observed_field} <= ?", start_date, end_date)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module RailsRbs
2
+ module Rules
3
+ # Used by the equality default rule type to ensure an objects observed_field equals a specified value.
4
+ module Equality
5
+ # Check one or more object's observed_Field to ensure it equals a specified value contained in the
6
+ # rule's enforced_value field.
7
+ # @param objects [Array<ActiveRecord::Base>] active record object, or objects that respond to the
8
+ # observed_field value of the including rule.
9
+ def follows_rule?(*objects)
10
+ objects.all? do |object|
11
+ source_value, dest_value = object.send(self.observed_field.to_sym), self.enforced_value
12
+ # Attempt to coerce the types if we can
13
+ if self.respond_to?(:enforced_type) && self.respond_to?(:force_type)
14
+ source_value, dest_value = force_type(source_value), force_type(dest_value)
15
+ end
16
+ # Check the comparison operator we use is correct
17
+ return source_value == dest_value if self.try(:comparison_operator).nil? || self.comparison_operator.to_s == '='
18
+ if comparison_operator.to_sym == :=~
19
+ dest_value = Regexp.new(Regexp.escape(dest_value), Regexp::IGNORECASE)
20
+ dest_value = /#{dest_value}/
21
+ end
22
+ source_value.send(self.try(:comparison_operator.to_sym), dest_value)
23
+ end
24
+ end
25
+ # Return an association of active record objects that have an observed_field value that matches
26
+ # the rules enforced_value. The association is made from the provided association using it as
27
+ # a base to query from.
28
+ # @param association Active record association, relation or collection proxy that supports the
29
+ # where interface
30
+ def filter_objects(association)
31
+ matching_value = self.enforced_value
32
+ # Attempt to coerce the types if we can
33
+ if self.respond_to?(:enforced_type) && self.respond_to?(:force_type)
34
+ matching_value = self.force_type(matching_value)
35
+ end
36
+ association.where("#{self.observed_field} = ?", matching_value)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ module RailsRbs
2
+ module Rules
3
+ # Used by the location default rule to ensure an object's observed field matches a set
4
+ # latitude and longitude for the rule. Assumes the rule supports a latitude and longitude
5
+ # method
6
+ module Location
7
+ # Check one or more object's observed_field to ensure it matches the rule's latitude and
8
+ # longitude values. This method assumes the provided object(s) observed_field will return
9
+ # an array of [latitude, longitude]
10
+ # @param objects [Array<ActiveRecord::Base>] active record objects, or objects that respond
11
+ # to the observed_field and return an array of [latitude, longitude] from it.
12
+ #
13
+ def follows_rule?(*objects)
14
+ objects.all? do |object|
15
+ # We expect the observed field to return an array of [latitude, longitude]
16
+ source_lat, source_long = *object.send(self.observed_field.to_sym)
17
+ dest_lat, dest_long = *[self.latitude, self.longitude]
18
+ # Attempt to coerce the types if we can
19
+ if self.respond_to?(:enforced_type) && self.respond_to?(:force_type)
20
+ [source_lat, source_long, dest_lat, dest_long].each { |val| force_type(val) }
21
+ end
22
+ # TODO: Use geocoding and distance of some sort here...
23
+ source_lat == dest_lat && source_long == dest_long
24
+ end
25
+ end
26
+ # Return an association of active record objects that match the latitude and longitude of
27
+ # the rule. This is done using the provided association as a base to query from.
28
+ # @param association Active record association, relation or collection proxy that supports
29
+ # the where interface.
30
+ def filter_objects(association)
31
+ matching_lat, matching_long = self.latitude, self.longitude
32
+ # Attempt to coerce the types if we can
33
+ if self.respond_to?(:enforced_type) && self.respond_to?(:force_type)
34
+ matching_lat, matching_long = force_type(matching_lat), force_type(matching_long)
35
+ end
36
+ association.where("latitude = ? AND longitude = ?", matching_lat, matching_long)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ module RailsRbs
2
+ module RunsAction
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ belongs_to :rule_set
7
+ validates_presence_of :enforced_field
8
+ validates_presence_of :enforced_value
9
+ end
10
+
11
+ # Apply the action to all objects in the provided collection. Application simply means
12
+ # the enforced_field will be set to the enforced_value.
13
+ # @param objects [Array<ActiveRecord::BAse> | ActiveRecord::Base] a single collection of active
14
+ # record objects to have this rule_action applied to.
15
+ def apply(*objects)
16
+ objects.flatten.map do |object|
17
+ if object.persisted?
18
+ response = object.update_column(self.enforced_field.to_sym, self.enforced_value)
19
+ else
20
+ response = object.send("#{self.enforced_field.to_sym}=", self.enforced_value)
21
+ end
22
+ [response, object.try(:errors)]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsRbs
4
+ VERSION = "0.2.2"
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rails_rbs do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_rbs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Rob
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-20 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: 5.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
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: rspec-rails
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: Simple rule base system for ruby on rails that utilizes DB tables through
56
+ active record
57
+ email:
58
+ - rob@everlance.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - MIT-LICENSE
64
+ - README.rdoc
65
+ - Rakefile
66
+ - app/controllers/rails_rbs/rule_sets_controller.rb
67
+ - app/controllers/rails_rbs/rules_controller.rb
68
+ - lib/generators/rails_rbs/migration_helpers.rb
69
+ - lib/generators/rails_rbs/rails_rbs_generator.rb
70
+ - lib/generators/rails_rbs/rule_generator.rb
71
+ - lib/generators/rails_rbs/templates/date_range_rule_migration.rb
72
+ - lib/generators/rails_rbs/templates/initializer.rb
73
+ - lib/generators/rails_rbs/templates/location_rule_migration.rb
74
+ - lib/generators/rails_rbs/templates/rule_action_migration.rb
75
+ - lib/generators/rails_rbs/templates/rule_migration.rb
76
+ - lib/generators/rails_rbs/templates/rule_set_migration.rb
77
+ - lib/rails_rbs.rb
78
+ - lib/rails_rbs/controllers/helpers.rb
79
+ - lib/rails_rbs/engine.rb
80
+ - lib/rails_rbs/follows_rule.rb
81
+ - lib/rails_rbs/follows_rule_set.rb
82
+ - lib/rails_rbs/models/follows_scope.rb
83
+ - lib/rails_rbs/rules/date_range.rb
84
+ - lib/rails_rbs/rules/equality.rb
85
+ - lib/rails_rbs/rules/location.rb
86
+ - lib/rails_rbs/runs_action.rb
87
+ - lib/rails_rbs/version.rb
88
+ - lib/tasks/rails_rbs_tasks.rake
89
+ homepage: https://github.com/Everlance/rails_rbs
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.6.14
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Simple rule base system for ruby on rails that utilizes DB tables through
113
+ active record
114
+ test_files: []