rails_rbs 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|