action_blocks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +33 -0
- data/app/assets/config/action_blocks.js +2 -0
- data/app/assets/javascripts/action_blocks/application.js +15 -0
- data/app/assets/stylesheets/action_blocks/application.css +15 -0
- data/app/controllers/action_blocks/attachments_controller.rb +22 -0
- data/app/controllers/action_blocks/barchart_blocks_controller.rb +14 -0
- data/app/controllers/action_blocks/base_controller.rb +22 -0
- data/app/controllers/action_blocks/blocks_controller.rb +16 -0
- data/app/controllers/action_blocks/command_blocks_controller.rb +6 -0
- data/app/controllers/action_blocks/form_blocks_controller.rb +13 -0
- data/app/controllers/action_blocks/model_blocks_controller.rb +13 -0
- data/app/controllers/action_blocks/table_blocks_controller.rb +13 -0
- data/app/controllers/action_blocks/workspace_blocks_controller.rb +6 -0
- data/app/helpers/action_blocks/application_helper.rb +4 -0
- data/app/jobs/action_blocks/application_job.rb +4 -0
- data/app/mailers/action_blocks/application_mailer.rb +6 -0
- data/app/models/action_blocks/application_record.rb +5 -0
- data/app/views/layouts/action_blocks/application.html.erb +16 -0
- data/config/initializers/action_blocks.rb +9 -0
- data/config/routes.rb +10 -0
- data/lib/action_block_loader.rb +120 -0
- data/lib/action_blocks.rb +76 -0
- data/lib/action_blocks/builders/authorization_builder.rb +21 -0
- data/lib/action_blocks/builders/barchart_builder.rb +48 -0
- data/lib/action_blocks/builders/base_builder.rb +221 -0
- data/lib/action_blocks/builders/block_type.rb +11 -0
- data/lib/action_blocks/builders/command_builder.rb +6 -0
- data/lib/action_blocks/builders/form_builder.rb +117 -0
- data/lib/action_blocks/builders/layout_builder.rb +15 -0
- data/lib/action_blocks/builders/model_builder.rb +566 -0
- data/lib/action_blocks/builders/summary_field_aggregation_functions.rb +41 -0
- data/lib/action_blocks/builders/table_builder.rb +259 -0
- data/lib/action_blocks/builders/workspace_builder.rb +282 -0
- data/lib/action_blocks/data_engine/authorization_adapter.rb +127 -0
- data/lib/action_blocks/data_engine/data_engine.rb +116 -0
- data/lib/action_blocks/data_engine/database_functions.rb +39 -0
- data/lib/action_blocks/data_engine/fields_engine.rb +103 -0
- data/lib/action_blocks/data_engine/filter_adapter.rb +105 -0
- data/lib/action_blocks/data_engine/filter_engine.rb +88 -0
- data/lib/action_blocks/data_engine/selections_via_where_engine.rb +134 -0
- data/lib/action_blocks/data_engine/summary_engine.rb +72 -0
- data/lib/action_blocks/engine.rb +5 -0
- data/lib/action_blocks/error.rb +62 -0
- data/lib/action_blocks/generator_helper.rb +134 -0
- data/lib/action_blocks/generators/action_blocks/model_block/USAGE +8 -0
- data/lib/action_blocks/generators/action_blocks/model_block/model_block_generator.rb +17 -0
- data/lib/action_blocks/generators/action_blocks/model_block/templates/model_block.rb +13 -0
- data/lib/action_blocks/generators/action_blocks/type/USAGE +8 -0
- data/lib/action_blocks/generators/action_blocks/type/templates/controller.rb +3 -0
- data/lib/action_blocks/generators/action_blocks/type/templates/dsl.rb +38 -0
- data/lib/action_blocks/generators/action_blocks/type/templates/type.css +3 -0
- data/lib/action_blocks/generators/action_blocks/type/templates/type.js +22 -0
- data/lib/action_blocks/generators/action_blocks/type/type_generator.rb +33 -0
- data/lib/action_blocks/store.rb +151 -0
- data/lib/action_blocks/version.rb +3 -0
- data/lib/generators/active_blocks/model_block/USAGE +8 -0
- data/lib/generators/active_blocks/model_block/model_block_generator.rb +17 -0
- data/lib/generators/active_blocks/model_block/templates/model_block.rb +13 -0
- data/lib/generators/active_blocks/type/USAGE +8 -0
- data/lib/generators/active_blocks/type/templates/controller.rb +3 -0
- data/lib/generators/active_blocks/type/templates/dsl.rb +38 -0
- data/lib/generators/active_blocks/type/templates/type.css +3 -0
- data/lib/generators/active_blocks/type/templates/type.js +22 -0
- data/lib/generators/active_blocks/type/type_generator.rb +33 -0
- data/lib/tasks/active_blocks_tasks.rake +4 -0
- metadata +128 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module ActionBlocks
|
|
2
|
+
# Data Engine
|
|
3
|
+
class AuthorizationAdapter
|
|
4
|
+
attr_accessor :engine, :user
|
|
5
|
+
|
|
6
|
+
def initialize(engine:, user:)
|
|
7
|
+
@engine = engine
|
|
8
|
+
@user = user
|
|
9
|
+
@model_id = @engine.root_klass.to_s.underscore
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# get the lisp/scheme like data structure specifying row level security
|
|
13
|
+
def rls_scheme
|
|
14
|
+
rls = ActionBlocks.find("rls-#{@model_id}-#{@user.role}")
|
|
15
|
+
if !rls
|
|
16
|
+
return Arel::Nodes::False.new # [:eq, Arel::Nodes::True.new, Arel::Nodes::False.new]
|
|
17
|
+
end
|
|
18
|
+
if rls.scheme == nil
|
|
19
|
+
return Arel::Nodes::True.new # [:eq, Arel::Nodes::True.new, Arel::Nodes::True.new]
|
|
20
|
+
end
|
|
21
|
+
return ActionBlocks.find("rls-#{@model_id}-#{@user.role}").scheme
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Extract fields from lisp/scheme
|
|
25
|
+
def get_fields(expression)
|
|
26
|
+
results = []
|
|
27
|
+
if expression.class == Array
|
|
28
|
+
fn, *args = expression
|
|
29
|
+
if fn == :user
|
|
30
|
+
return []
|
|
31
|
+
else
|
|
32
|
+
return args.map { |a| get_fields(a) }.flatten.uniq
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
if expression.class == Symbol
|
|
36
|
+
return expression
|
|
37
|
+
end
|
|
38
|
+
return []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Convert all fields to arel nodes while building up needed @engine.joins
|
|
42
|
+
def get_arel_attributes()
|
|
43
|
+
@fields = get_fields(rls_scheme)
|
|
44
|
+
@arel_attributes = {}
|
|
45
|
+
[@fields].flatten.each do |f|
|
|
46
|
+
f = ActionBlocks.find("field-#{@model_id}-#{f}")
|
|
47
|
+
select_req = f.select_requirements
|
|
48
|
+
if select_req[:type] == :summary
|
|
49
|
+
raise "Summary fields not supported in authorizations"
|
|
50
|
+
end
|
|
51
|
+
field_name = select_req[:field_name]
|
|
52
|
+
node, *rest = select_req[:path]
|
|
53
|
+
@arel_attributes[field_name] = walk_path(@engine.root_klass, node, @engine.root_key, rest)
|
|
54
|
+
end
|
|
55
|
+
return @arel_attributes
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def evaluate(expression)
|
|
59
|
+
# Convert Symbol to Arel Attribute
|
|
60
|
+
if expression.class == Symbol
|
|
61
|
+
return @arel_attributes[expression]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Convert Proc to it's result
|
|
65
|
+
if expression.class == Proc
|
|
66
|
+
proc_args = {}
|
|
67
|
+
# debug expression.parameters
|
|
68
|
+
if expression.parameters.include?([:keyreq, :user])
|
|
69
|
+
proc_args[:user] = @user
|
|
70
|
+
end
|
|
71
|
+
return expression.call(**proc_args)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Convert expression to Arel Predicate
|
|
75
|
+
if expression.class == Array
|
|
76
|
+
fn, *args = expression
|
|
77
|
+
case fn
|
|
78
|
+
when :user
|
|
79
|
+
return @user.send(args[0])
|
|
80
|
+
when :eq
|
|
81
|
+
left, right = args
|
|
82
|
+
return evaluate(left).eq(evaluate(right))
|
|
83
|
+
when :not_eq
|
|
84
|
+
left, right = args
|
|
85
|
+
return evaluate(left).not_eq(evaluate(right))
|
|
86
|
+
when :and
|
|
87
|
+
return args.map {|x| evaluate(x)}.reduce(&:and)
|
|
88
|
+
when :or
|
|
89
|
+
return args.map {|x| evaluate(x)}.reduce(&:or)
|
|
90
|
+
else
|
|
91
|
+
raise "RLS function #{fn.inspect} not recognized"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
return expression
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def process
|
|
98
|
+
@arel_attributes = get_arel_attributes()
|
|
99
|
+
@engine.wheres << evaluate(rls_scheme)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def walk_path(klass, node, parent_key, col_path)
|
|
103
|
+
key = [parent_key, node].compact.join('_').to_sym
|
|
104
|
+
if node.class != Symbol
|
|
105
|
+
return node
|
|
106
|
+
end
|
|
107
|
+
if !col_path.empty?
|
|
108
|
+
# Create Arel Table Alias
|
|
109
|
+
relation = klass.reflections[node.to_s]
|
|
110
|
+
klass = relation.klass
|
|
111
|
+
@engine.tables[key] = klass.arel_table.alias(key) unless @engine.tables[key]
|
|
112
|
+
# Create Join
|
|
113
|
+
fk = relation.join_foreign_key
|
|
114
|
+
pk = relation.join_primary_key
|
|
115
|
+
join_on = @engine.tables[key].create_on(@engine.tables[parent_key][fk].eq(@engine.tables[key][pk]))
|
|
116
|
+
@engine.joins[key] = @engine.tables[parent_key].create_join(@engine.tables[key], join_on, Arel::Nodes::OuterJoin)
|
|
117
|
+
# Recurse
|
|
118
|
+
next_node, *rest = col_path
|
|
119
|
+
return walk_path(klass, next_node, key, rest)
|
|
120
|
+
else
|
|
121
|
+
return @engine.tables[parent_key][node.to_sym]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module ActionBlocks
|
|
2
|
+
# Data Engine
|
|
3
|
+
class DataEngine
|
|
4
|
+
def initialize(root_klass,
|
|
5
|
+
user: nil,
|
|
6
|
+
table_alias_prefix: nil,
|
|
7
|
+
select_reqs: [],
|
|
8
|
+
select_fields: [],
|
|
9
|
+
filter_reqs: [],
|
|
10
|
+
selection_match_reqs: [],
|
|
11
|
+
selection_filter_reqs: []
|
|
12
|
+
)
|
|
13
|
+
@root_klass = root_klass
|
|
14
|
+
|
|
15
|
+
@filter_reqs = filter_reqs
|
|
16
|
+
|
|
17
|
+
select_reqs_via_fields = select_fields.map(&:select_requirements)
|
|
18
|
+
|
|
19
|
+
if [select_reqs].length > 0
|
|
20
|
+
Rails.logger.warn "Passing select_reqs to Data Engine is deprecated."
|
|
21
|
+
end
|
|
22
|
+
all_select_reqs = [select_reqs, select_reqs_via_fields].flatten.compact
|
|
23
|
+
|
|
24
|
+
select_reqs_for_field_engine = all_select_reqs.select { |r| r[:type].nil? }
|
|
25
|
+
select_reqs_for_summary_engine = all_select_reqs.select { |r| r[:type] == :summary }
|
|
26
|
+
|
|
27
|
+
if ActionBlocks.config[:should_authorize]
|
|
28
|
+
if user.nil?
|
|
29
|
+
raise "@user must be provided to data engine when should_authorize is configured"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
@user = user
|
|
33
|
+
|
|
34
|
+
@fields_engine = ActionBlocks.config[:fields_engine].new(
|
|
35
|
+
@root_klass,
|
|
36
|
+
user: user,
|
|
37
|
+
table_alias_prefix: table_alias_prefix,
|
|
38
|
+
select_reqs: select_reqs_for_field_engine
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@selections_engine = ActionBlocks.config[:selections_engine].new(
|
|
42
|
+
@root_klass,
|
|
43
|
+
user: user,
|
|
44
|
+
table_alias_prefix: table_alias_prefix,
|
|
45
|
+
selection_match_reqs: selection_match_reqs,
|
|
46
|
+
selection_filter_reqs: selection_filter_reqs
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# @filter_engine = ActionBlocks.config[:filter_engine].new(
|
|
50
|
+
# @root_klass,
|
|
51
|
+
# user: user,
|
|
52
|
+
# filter_reqs: filter_reqs,
|
|
53
|
+
# )
|
|
54
|
+
|
|
55
|
+
@summary_engine = ActionBlocks.config[:summary_engine].new(
|
|
56
|
+
@root_klass,
|
|
57
|
+
user: user,
|
|
58
|
+
summary_reqs: select_reqs_for_summary_engine
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# if ActionBlocks.config[:should_authorize]
|
|
62
|
+
# @authorization_engine = ActionBlocks.config[:authorization_engine].new(
|
|
63
|
+
# @root_klass,
|
|
64
|
+
# user: user
|
|
65
|
+
# # table_alias_prefix: table_alias_prefix,
|
|
66
|
+
# # select_reqs: select_reqs_for_field_engine
|
|
67
|
+
# )
|
|
68
|
+
# end
|
|
69
|
+
|
|
70
|
+
process
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def process
|
|
74
|
+
@fields_engine.process
|
|
75
|
+
@selections_engine.process
|
|
76
|
+
@summary_engine.process
|
|
77
|
+
# @filter_engine.process
|
|
78
|
+
|
|
79
|
+
@filter_adapter = FilterAdapter.new(engine: @fields_engine, user: @user, filter_reqs: @filter_reqs)
|
|
80
|
+
@filter_adapter.process
|
|
81
|
+
|
|
82
|
+
if ActionBlocks.config[:should_authorize]
|
|
83
|
+
@authorization_adapter = AuthorizationAdapter.new(engine: @fields_engine, user: @user)
|
|
84
|
+
@authorization_adapter.process
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_json
|
|
91
|
+
sql = query.to_sql
|
|
92
|
+
jsql = "select array_to_json(array_agg(row_to_json(t)))
|
|
93
|
+
from (#{sql}) t"
|
|
94
|
+
ActiveRecord::Base.connection.select_value(jsql)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Experimental
|
|
98
|
+
def first_to_json
|
|
99
|
+
# SELECT row_to_json(r)
|
|
100
|
+
sql = query.to_sql
|
|
101
|
+
jsql = "select row_to_json(t)
|
|
102
|
+
from (#{sql}) t"
|
|
103
|
+
ActiveRecord::Base.connection.select_value(jsql)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def query
|
|
107
|
+
engine_queries = [
|
|
108
|
+
@summary_engine.query,
|
|
109
|
+
@selections_engine.query,
|
|
110
|
+
@fields_engine.query,
|
|
111
|
+
# @filter_engine.query,
|
|
112
|
+
]
|
|
113
|
+
engine_queries.reduce(&:merge)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module ActionBlocks
|
|
2
|
+
class DatabaseFunctions
|
|
3
|
+
# methods define their own params, always followed by current node and current user
|
|
4
|
+
def timezone(tz, node, user, *args)
|
|
5
|
+
utc = Arel::Nodes::NamedFunction.new(
|
|
6
|
+
'timezone',
|
|
7
|
+
[Arel::Nodes.build_quoted('UTC'), node]
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
Arel::Nodes::NamedFunction.new(
|
|
11
|
+
'timezone',
|
|
12
|
+
[Arel::Nodes.build_quoted(tz), utc]
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def count(node, *args)
|
|
17
|
+
Arel::Nodes::NamedFunction.new(
|
|
18
|
+
'count',
|
|
19
|
+
[node]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def string_agg(delimiter, node, *args)
|
|
24
|
+
Arel::Nodes::NamedFunction.new(
|
|
25
|
+
'string_agg',
|
|
26
|
+
[node, Arel::Nodes.build_quoted(delimiter)]
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def every(predicate, value, node, *args)
|
|
31
|
+
every_part = Arel::Nodes::NamedFunction.new(
|
|
32
|
+
'every',
|
|
33
|
+
[node.send(predicate, Arel::Nodes.build_quoted(value))]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
Arel::Nodes::NamedFunction.new('CAST', [every_part.as('TEXT')])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module ActionBlocks
|
|
2
|
+
# Data Engine
|
|
3
|
+
class FieldsEngine
|
|
4
|
+
attr_accessor :tables, :root_klass, :select_reqs, :selects, :joins,
|
|
5
|
+
:root_key, :joins, :wheres
|
|
6
|
+
|
|
7
|
+
def initialize(root_klass, user: nil, table_alias_prefix:, select_reqs: [])
|
|
8
|
+
@root_klass = root_klass
|
|
9
|
+
@user = user
|
|
10
|
+
@table_alias_prefix = table_alias_prefix
|
|
11
|
+
@select_reqs = select_reqs
|
|
12
|
+
@tables = {}
|
|
13
|
+
@selects = []
|
|
14
|
+
@joins = {}
|
|
15
|
+
@wheres = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process
|
|
19
|
+
root_table = @root_klass.arel_table.alias([@table_alias_prefix, @root_klass.to_s.underscore.pluralize].compact.join('_'))
|
|
20
|
+
@root_key = [@table_alias_prefix, @root_klass.to_s.underscore.pluralize].compact.join('_').to_sym
|
|
21
|
+
|
|
22
|
+
# Add base table to tables
|
|
23
|
+
@tables[@root_key.to_sym] = root_table
|
|
24
|
+
|
|
25
|
+
# Add needed relations to tables
|
|
26
|
+
@select_reqs.each do |selectreq|
|
|
27
|
+
# binding.pry
|
|
28
|
+
colname = selectreq[:field_name]
|
|
29
|
+
colpath = selectreq[:path]
|
|
30
|
+
function = selectreq[:function]
|
|
31
|
+
node, *rest = colpath
|
|
32
|
+
walk_colpath(@root_klass, node, @root_key, rest, colname, function)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def walk_colpath(klass, node, parent_key, col_path, colname, function)
|
|
37
|
+
key = [@table_alias_prefix, parent_key, node].compact.join('_').to_sym
|
|
38
|
+
if !col_path.empty?
|
|
39
|
+
|
|
40
|
+
next_klass = create_table_and_joins(klass, node, key, parent_key)
|
|
41
|
+
|
|
42
|
+
# Recurse
|
|
43
|
+
next_node, *rest = col_path
|
|
44
|
+
walk_colpath(next_klass, next_node, key, rest, colname, function)
|
|
45
|
+
else
|
|
46
|
+
# Create Arel Select
|
|
47
|
+
select = if function.nil?
|
|
48
|
+
@tables[parent_key][node.to_sym].as(colname.to_s)
|
|
49
|
+
else
|
|
50
|
+
DatabaseFunctions.new.instance_exec(@tables[parent_key][node.to_sym], @user, &function).as(colname.to_s)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@selects << select
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def params_to_arel(aggregate_params)
|
|
58
|
+
aggregate_params.map { |param| param.is_a?(String) ? Arel::Nodes.build_quoted(param) : param } if aggregate_params
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def create_table_and_joins(klass, node, key, parent_key, join_prefix = nil, associations = nil)
|
|
62
|
+
# Create Arel Table Alias
|
|
63
|
+
relation = klass.reflections[node.to_s] if node.is_a? Symbol
|
|
64
|
+
unless @tables[key]
|
|
65
|
+
@tables[key] = (relation ? relation.klass : node).arel_table.alias(key) unless @tables[key]
|
|
66
|
+
|
|
67
|
+
# Create Join
|
|
68
|
+
fk = associations ? associations[parent_key.to_s][:foreign_key] : relation.join_foreign_key
|
|
69
|
+
pk = associations ? associations[parent_key.to_s][:primary_key] : relation.join_primary_key
|
|
70
|
+
join_on = @tables[key].create_on(@tables[parent_key][fk].eq(@tables[key][pk]))
|
|
71
|
+
@joins[join_prefix ? [join_prefix, node.to_s.underscore].compact.join('_').to_sym : key] = @tables[parent_key].create_join(@tables[key], join_on, Arel::Nodes::OuterJoin)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
relation ? relation.klass : node
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def selects
|
|
78
|
+
@selects
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def ordered_joins
|
|
82
|
+
@joins.values
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def froms
|
|
86
|
+
@root_klass.arel_table.alias([@table_alias_prefix, @root_klass.to_s.underscore.pluralize].compact.join('_'))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def wheres
|
|
90
|
+
@wheres
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def query
|
|
94
|
+
@root_klass
|
|
95
|
+
.from(froms)
|
|
96
|
+
.select(selects)
|
|
97
|
+
.joins(ordered_joins)
|
|
98
|
+
.where(wheres.compact.reduce(&:and))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module ActionBlocks
|
|
2
|
+
# Data Engine
|
|
3
|
+
class FilterAdapter
|
|
4
|
+
attr_accessor :engine, :user, :filter_reqs
|
|
5
|
+
|
|
6
|
+
def initialize(engine:, filter_reqs:, user:)
|
|
7
|
+
@engine = engine
|
|
8
|
+
@user = user
|
|
9
|
+
@rls_scheme = filter_reqs
|
|
10
|
+
@model_id = @engine.root_klass.to_s.underscore
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Extract fields from lisp/scheme
|
|
14
|
+
def get_fields(expression)
|
|
15
|
+
if expression.class == Array
|
|
16
|
+
fn, *args = expression
|
|
17
|
+
return [] if fn == :user
|
|
18
|
+
return args.map { |a| get_fields(a) }.flatten.uniq
|
|
19
|
+
end
|
|
20
|
+
return expression if expression.class == Symbol
|
|
21
|
+
return []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Convert all fields to arel nodes while building up needed @engine.joins
|
|
25
|
+
def get_arel_attributes()
|
|
26
|
+
@fields = get_fields(@rls_scheme)
|
|
27
|
+
@arel_attributes = {}
|
|
28
|
+
[@fields].flatten.each do |f|
|
|
29
|
+
f = ActionBlocks.find("field-#{@model_id}-#{f}")
|
|
30
|
+
select_req = f.select_requirements
|
|
31
|
+
if select_req[:type] == :summary
|
|
32
|
+
raise "Summary fields not supported in authorizations"
|
|
33
|
+
end
|
|
34
|
+
field_name = select_req[:field_name]
|
|
35
|
+
node, *rest = select_req[:path]
|
|
36
|
+
@arel_attributes[field_name] = walk_path(@engine.root_klass, node, @engine.root_key, rest)
|
|
37
|
+
end
|
|
38
|
+
return @arel_attributes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def evaluate(expression)
|
|
42
|
+
# Convert Symbol to Arel Attribute
|
|
43
|
+
return @arel_attributes[expression] if expression.class == Symbol
|
|
44
|
+
|
|
45
|
+
# Convert Proc to it's result
|
|
46
|
+
if expression.class == Proc
|
|
47
|
+
proc_args = {}
|
|
48
|
+
# debug expression.parameters
|
|
49
|
+
if expression.parameters.include?([:keyreq, :user])
|
|
50
|
+
proc_args[:user] = @user
|
|
51
|
+
end
|
|
52
|
+
return expression.call(**proc_args)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Convert expression to Arel Predicate
|
|
56
|
+
if expression.class == Array
|
|
57
|
+
fn, *args = expression
|
|
58
|
+
case fn
|
|
59
|
+
when :user
|
|
60
|
+
return @user.send(args[0])
|
|
61
|
+
when :eq
|
|
62
|
+
left, right = args
|
|
63
|
+
return evaluate(left).eq(evaluate(right))
|
|
64
|
+
when :not_eq
|
|
65
|
+
left, right = args
|
|
66
|
+
return evaluate(left).not_eq(evaluate(right))
|
|
67
|
+
when :and
|
|
68
|
+
return args.map {|x| evaluate(x)}.reduce(&:and)
|
|
69
|
+
when :or
|
|
70
|
+
return args.map {|x| evaluate(x)}.reduce(&:or)
|
|
71
|
+
else
|
|
72
|
+
raise "RLS function #{fn.inspect} not recognized"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return expression
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def process
|
|
80
|
+
if (!@rls_scheme.empty?)
|
|
81
|
+
@arel_attributes = get_arel_attributes()
|
|
82
|
+
@engine.wheres << evaluate(@rls_scheme)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def walk_path(klass, node, parent_key, col_path)
|
|
87
|
+
key = [parent_key, node].compact.join('_').to_sym
|
|
88
|
+
return node if node.class != Symbol
|
|
89
|
+
return @engine.tables[parent_key][node.to_sym] if col_path.empty?
|
|
90
|
+
|
|
91
|
+
# Create Arel Table Alias
|
|
92
|
+
relation = klass.reflections[node.to_s]
|
|
93
|
+
klass = relation.klass
|
|
94
|
+
@engine.tables[key] = klass.arel_table.alias(key) unless @engine.tables[key]
|
|
95
|
+
# Create Join
|
|
96
|
+
fk = relation.join_foreign_key
|
|
97
|
+
pk = relation.join_primary_key
|
|
98
|
+
join_on = @engine.tables[key].create_on(@engine.tables[parent_key][fk].eq(@engine.tables[key][pk]))
|
|
99
|
+
@engine.joins[key] = @engine.tables[parent_key].create_join(@engine.tables[key], join_on, Arel::Nodes::OuterJoin)
|
|
100
|
+
# Recurse
|
|
101
|
+
next_node, *rest = col_path
|
|
102
|
+
return walk_path(klass, next_node, key, rest)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|