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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +23 -0
- data/app/controllers/rails_rbs/rule_sets_controller.rb +139 -0
- data/app/controllers/rails_rbs/rules_controller.rb +114 -0
- data/lib/generators/rails_rbs/migration_helpers.rb +45 -0
- data/lib/generators/rails_rbs/rails_rbs_generator.rb +95 -0
- data/lib/generators/rails_rbs/rule_generator.rb +62 -0
- data/lib/generators/rails_rbs/templates/date_range_rule_migration.rb +9 -0
- data/lib/generators/rails_rbs/templates/initializer.rb +27 -0
- data/lib/generators/rails_rbs/templates/location_rule_migration.rb +9 -0
- data/lib/generators/rails_rbs/templates/rule_action_migration.rb +10 -0
- data/lib/generators/rails_rbs/templates/rule_migration.rb +14 -0
- data/lib/generators/rails_rbs/templates/rule_set_migration.rb +9 -0
- data/lib/rails_rbs.rb +64 -0
- data/lib/rails_rbs/controllers/helpers.rb +33 -0
- data/lib/rails_rbs/engine.rb +4 -0
- data/lib/rails_rbs/follows_rule.rb +65 -0
- data/lib/rails_rbs/follows_rule_set.rb +52 -0
- data/lib/rails_rbs/models/follows_scope.rb +55 -0
- data/lib/rails_rbs/rules/date_range.rb +41 -0
- data/lib/rails_rbs/rules/equality.rb +40 -0
- data/lib/rails_rbs/rules/location.rb +40 -0
- data/lib/rails_rbs/runs_action.rb +26 -0
- data/lib/rails_rbs/version.rb +5 -0
- data/lib/tasks/rails_rbs_tasks.rake +4 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -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,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,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
|
data/lib/rails_rbs.rb
ADDED
@@ -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,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
|
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: []
|