rails_rbs 0.2.2

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
+ 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: []