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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +121 -0
  4. data/Rakefile +33 -0
  5. data/app/assets/config/action_blocks.js +2 -0
  6. data/app/assets/javascripts/action_blocks/application.js +15 -0
  7. data/app/assets/stylesheets/action_blocks/application.css +15 -0
  8. data/app/controllers/action_blocks/attachments_controller.rb +22 -0
  9. data/app/controllers/action_blocks/barchart_blocks_controller.rb +14 -0
  10. data/app/controllers/action_blocks/base_controller.rb +22 -0
  11. data/app/controllers/action_blocks/blocks_controller.rb +16 -0
  12. data/app/controllers/action_blocks/command_blocks_controller.rb +6 -0
  13. data/app/controllers/action_blocks/form_blocks_controller.rb +13 -0
  14. data/app/controllers/action_blocks/model_blocks_controller.rb +13 -0
  15. data/app/controllers/action_blocks/table_blocks_controller.rb +13 -0
  16. data/app/controllers/action_blocks/workspace_blocks_controller.rb +6 -0
  17. data/app/helpers/action_blocks/application_helper.rb +4 -0
  18. data/app/jobs/action_blocks/application_job.rb +4 -0
  19. data/app/mailers/action_blocks/application_mailer.rb +6 -0
  20. data/app/models/action_blocks/application_record.rb +5 -0
  21. data/app/views/layouts/action_blocks/application.html.erb +16 -0
  22. data/config/initializers/action_blocks.rb +9 -0
  23. data/config/routes.rb +10 -0
  24. data/lib/action_block_loader.rb +120 -0
  25. data/lib/action_blocks.rb +76 -0
  26. data/lib/action_blocks/builders/authorization_builder.rb +21 -0
  27. data/lib/action_blocks/builders/barchart_builder.rb +48 -0
  28. data/lib/action_blocks/builders/base_builder.rb +221 -0
  29. data/lib/action_blocks/builders/block_type.rb +11 -0
  30. data/lib/action_blocks/builders/command_builder.rb +6 -0
  31. data/lib/action_blocks/builders/form_builder.rb +117 -0
  32. data/lib/action_blocks/builders/layout_builder.rb +15 -0
  33. data/lib/action_blocks/builders/model_builder.rb +566 -0
  34. data/lib/action_blocks/builders/summary_field_aggregation_functions.rb +41 -0
  35. data/lib/action_blocks/builders/table_builder.rb +259 -0
  36. data/lib/action_blocks/builders/workspace_builder.rb +282 -0
  37. data/lib/action_blocks/data_engine/authorization_adapter.rb +127 -0
  38. data/lib/action_blocks/data_engine/data_engine.rb +116 -0
  39. data/lib/action_blocks/data_engine/database_functions.rb +39 -0
  40. data/lib/action_blocks/data_engine/fields_engine.rb +103 -0
  41. data/lib/action_blocks/data_engine/filter_adapter.rb +105 -0
  42. data/lib/action_blocks/data_engine/filter_engine.rb +88 -0
  43. data/lib/action_blocks/data_engine/selections_via_where_engine.rb +134 -0
  44. data/lib/action_blocks/data_engine/summary_engine.rb +72 -0
  45. data/lib/action_blocks/engine.rb +5 -0
  46. data/lib/action_blocks/error.rb +62 -0
  47. data/lib/action_blocks/generator_helper.rb +134 -0
  48. data/lib/action_blocks/generators/action_blocks/model_block/USAGE +8 -0
  49. data/lib/action_blocks/generators/action_blocks/model_block/model_block_generator.rb +17 -0
  50. data/lib/action_blocks/generators/action_blocks/model_block/templates/model_block.rb +13 -0
  51. data/lib/action_blocks/generators/action_blocks/type/USAGE +8 -0
  52. data/lib/action_blocks/generators/action_blocks/type/templates/controller.rb +3 -0
  53. data/lib/action_blocks/generators/action_blocks/type/templates/dsl.rb +38 -0
  54. data/lib/action_blocks/generators/action_blocks/type/templates/type.css +3 -0
  55. data/lib/action_blocks/generators/action_blocks/type/templates/type.js +22 -0
  56. data/lib/action_blocks/generators/action_blocks/type/type_generator.rb +33 -0
  57. data/lib/action_blocks/store.rb +151 -0
  58. data/lib/action_blocks/version.rb +3 -0
  59. data/lib/generators/active_blocks/model_block/USAGE +8 -0
  60. data/lib/generators/active_blocks/model_block/model_block_generator.rb +17 -0
  61. data/lib/generators/active_blocks/model_block/templates/model_block.rb +13 -0
  62. data/lib/generators/active_blocks/type/USAGE +8 -0
  63. data/lib/generators/active_blocks/type/templates/controller.rb +3 -0
  64. data/lib/generators/active_blocks/type/templates/dsl.rb +38 -0
  65. data/lib/generators/active_blocks/type/templates/type.css +3 -0
  66. data/lib/generators/active_blocks/type/templates/type.js +22 -0
  67. data/lib/generators/active_blocks/type/type_generator.rb +33 -0
  68. data/lib/tasks/active_blocks_tasks.rake +4 -0
  69. metadata +128 -0
@@ -0,0 +1,88 @@
1
+ module ActionBlocks
2
+ # Data Engine
3
+ class FilterEngine
4
+ attr_accessor :tables, :root_klass, :root_key, :froms, :joins, :wheres,
5
+ :selects, :joins, :filter_reqs
6
+
7
+ def initialize(root_klass, user: nil, filter_reqs: [])
8
+ @root_klass = root_klass
9
+ @filter_reqs = filter_reqs
10
+ @tables = {}
11
+ @joins = {}
12
+ @wheres = []
13
+ @froms = []
14
+ end
15
+
16
+ def process
17
+ @root_table = @root_klass.arel_table
18
+ @root_key = @root_klass.to_s.underscore.to_sym
19
+
20
+ # Add base table to tables
21
+ @tables[@root_key.to_sym] = @root_table
22
+
23
+ [@filter_reqs].flatten.each do |matchreq|
24
+ node, *rest = matchreq[:path1]
25
+ # puts "base node: #{node} rest #{rest}"
26
+ base_expression = walk_filter_path(@root_klass, node, @root_key, rest)
27
+
28
+ node, *rest = matchreq[:path2]
29
+ # puts "related node: #{node} rest #{rest}"
30
+ related_expression = walk_filter_path(@root_klass, node, @root_key, rest)
31
+
32
+ if base_expression.class.ancestors.include?(Arel::Attributes::Attribute)
33
+ where = base_expression.send(matchreq[:predicate], related_expression)
34
+ else
35
+ where = related_expression.send(matchreq[:predicate], base_expression)
36
+ end
37
+ @wheres << where
38
+
39
+ end
40
+
41
+ end
42
+
43
+ def walk_filter_path(klass, node, parent_key, col_path)
44
+ key = [parent_key, node].compact.join('_').to_sym
45
+ if node.class != Symbol
46
+ return node
47
+ end
48
+ if !col_path.empty?
49
+ # Create Arel Table Alias
50
+ relation = klass.reflections[node.to_s]
51
+ klass = relation.klass
52
+ @tables[key] = klass.arel_table.alias(key) unless @tables[key]
53
+ # Create Join
54
+ fk = relation.join_foreign_key
55
+ pk = relation.join_primary_key
56
+ join_on = @tables[key].create_on(@tables[parent_key][fk].eq(@tables[key][pk]))
57
+ @joins[key] = @tables[parent_key].create_join(@tables[key], join_on, Arel::Nodes::OuterJoin)
58
+ # Recurse
59
+ next_node, *rest = col_path
60
+ return walk_filter_path(klass, next_node, key, rest)
61
+ else
62
+ # Return expression
63
+ # puts "parent_key: #{node.to_sym}"
64
+ # puts "node: #{node.to_sym}"
65
+ return @tables[parent_key][node.to_sym]
66
+ end
67
+ end
68
+
69
+ def froms
70
+ []
71
+ end
72
+
73
+ def ordered_joins
74
+ @joins.values
75
+ end
76
+
77
+ def wheres
78
+ @wheres.reduce(&:and)
79
+ end
80
+
81
+ def query
82
+ @root_klass
83
+ .from(froms)
84
+ .joins(ordered_joins)
85
+ .where(wheres)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,134 @@
1
+ module ActionBlocks
2
+ # Data Engine
3
+ class SelectionsViaWhereEngine
4
+ attr_accessor :tables, :root_klass, :selection_match_reqs, :selects, :joins, :selection_filter_reqs
5
+
6
+ def initialize(root_klass, user: nil, table_alias_prefix: nil, type: :many_to_many, selection_filter_reqs: [], selection_match_reqs: [], additional_where: nil)
7
+ @root_klass = root_klass
8
+ @table_alias_prefix = table_alias_prefix
9
+ @tables = {}
10
+ @joins = {}
11
+ @wheres = []
12
+ @froms = []
13
+ @type = type
14
+
15
+ @additional_where = additional_where
16
+
17
+ # I named them selection_match_reqs because
18
+ # the DataEngine may work with match_reqs in
19
+ # different contexts. It may use them for
20
+ # summary fields or it may use them for
21
+ # a filtering a query to the children of some
22
+ # base models 'children'
23
+ @selection_match_reqs = selection_match_reqs
24
+ @selection_filter_reqs = selection_filter_reqs
25
+ end
26
+
27
+ def process
28
+ @root_table = @root_klass.arel_table.alias([@table_alias_prefix, @root_klass.to_s.underscore.pluralize].compact.join('_'))
29
+ root_key = [@table_alias_prefix, @root_klass.to_s.underscore.pluralize].compact.join('_').to_sym
30
+
31
+ @froms << @root_table
32
+ # Add base table to tables
33
+ @tables[root_key.to_sym] = @root_table
34
+
35
+ [@selection_match_reqs, @selection_filter_reqs].flatten.compact.each do |matchreq|
36
+ node, *rest = matchreq[:base_path]
37
+ # puts "base node: #{node} rest #{rest}"
38
+ base_expression = walk_selection_match_path(@root_klass, node, root_key, rest)
39
+
40
+ node, *rest = matchreq[:related_path]
41
+ # puts "related node: #{node} rest #{rest}"
42
+ related_expression = walk_selection_match_path(@root_klass, node, root_key, rest)
43
+
44
+ where = if base_expression.class.ancestors.include?(Arel::Attributes::Attribute)
45
+ base_expression.send(matchreq[:predicate], related_expression)
46
+ else
47
+ related_expression.send(matchreq[:predicate], base_expression)
48
+ end
49
+ @wheres << where
50
+ end
51
+ end
52
+
53
+ def walk_selection_match_path(klass, node, parent_key, col_path)
54
+ # puts "klass: #{klass} node: #{node} parent_key #{parent_key.inspect} col_path #{col_path.inspect}"
55
+ # pp [key, rest_col_path]
56
+ key = if node.class == Class
57
+ [@table_alias_prefix, node.to_s.underscore.pluralize].compact.join('_').to_sym
58
+ else
59
+ [@table_alias_prefix, parent_key, node].compact.join('_').to_sym
60
+ end
61
+ return node if node.class != Symbol && node.class != Class
62
+ if !col_path.empty?
63
+ # Create Arel Table Alias
64
+ if node.class != Class
65
+ relation = klass.reflections[node.to_s]
66
+ klass = relation.klass
67
+ @tables[key] = klass.arel_table.alias(key) unless @tables[key]
68
+ # Create Join
69
+ fk = relation.join_foreign_key
70
+ pk = relation.join_primary_key
71
+ join_on = @tables[key].create_on(@tables[parent_key][fk].eq(@tables[key][pk]))
72
+ @joins[key] = @tables[parent_key].create_join(@tables[key], join_on, Arel::Nodes::OuterJoin)
73
+ else
74
+ klass = node
75
+ unless @tables[key]
76
+ @tables[key] = klass.arel_table.alias(key) unless @tables[key]
77
+ @froms << @tables[key]
78
+ end
79
+ end
80
+ # Recurse
81
+ next_node, *rest = col_path
82
+ return walk_selection_match_path(klass, next_node, key, rest)
83
+ else
84
+ # Return expression
85
+ # puts "parent_key: #{node.to_sym}"
86
+ # puts "node: #{node.to_sym}"
87
+ return @tables[parent_key][node.to_sym]
88
+ end
89
+ end
90
+
91
+
92
+ def froms
93
+ if @type == :many_to_many
94
+ [@root_table]
95
+ else
96
+ @froms
97
+ end
98
+ end
99
+
100
+ def ordered_joins
101
+ if @type == :many_to_many
102
+ []
103
+ else
104
+ @joins.values
105
+ end
106
+ end
107
+
108
+ def wheres
109
+ @wheres << @additional_where if @additional_where
110
+ if @type == :many_to_many && (!@selection_match_reqs.empty? || !@selection_filter_reqs.empty?)
111
+ subquery_arel = subquery_for_many_to_many_selections.arel
112
+ # w = Arel::Nodes::In.new(@root_table[:id], subquery.ast)
113
+ @root_table[:id].in(subquery_arel)
114
+ else
115
+ @wheres.reduce(&:and)
116
+ end
117
+ end
118
+
119
+ def subquery_for_many_to_many_selections
120
+ # Arel::Distinct.new(@rook_klasas[:id])
121
+ @root_klass
122
+ .from([@froms].flatten)
123
+ .select(@root_table[:id])
124
+ .joins(@joins.values)
125
+ .where(@wheres.reduce(&:and))
126
+ end
127
+
128
+ def query
129
+ @root_klass
130
+ .joins(ordered_joins)
131
+ .where(wheres)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,72 @@
1
+ module ActionBlocks
2
+ # Data Engine
3
+ class SummaryEngine
4
+ attr_accessor :tables, :root_klass, :summary_reqs, :selects, :joins
5
+
6
+ def initialize(root_klass, user: nil, summary_reqs: [])
7
+ @root_klass = root_klass
8
+ @tables = {}
9
+
10
+ @user = user
11
+
12
+ @selects = []
13
+
14
+ @summary_reqs = summary_reqs
15
+ end
16
+
17
+ def process
18
+ root_table = @root_klass.arel_table.alias(@root_klass.to_s.underscore.pluralize)
19
+ sub_table = @root_klass.arel_table.alias(['sub', @root_klass.to_s.underscore.pluralize].join('_'))
20
+
21
+ @summary_reqs.each do |summaryreq|
22
+ select_reqs = [summaryreq[:select_req]]
23
+ match_reqs = summaryreq[:match_reqs]
24
+ filter_reqs = summaryreq[:filter_reqs]
25
+
26
+ @fields_engine = ActionBlocks.config[:fields_engine].new(
27
+ summaryreq[:root_klass],
28
+ table_alias_prefix: 'sub',
29
+ select_reqs: select_reqs
30
+ )
31
+
32
+ # @selections_engine = SelectionsViaJoinsEngine.new(
33
+ @selections_engine = ActionBlocks.config[:selections_engine].new(
34
+ summaryreq[:root_klass],
35
+ table_alias_prefix: 'sub',
36
+ selection_match_reqs: match_reqs,
37
+ selection_filter_reqs: filter_reqs,
38
+ additional_where: root_table[:id].eq(sub_table[:id])
39
+ )
40
+
41
+ @fields_engine.process
42
+ @selections_engine.process
43
+
44
+ if ActionBlocks.config[:should_authorize]
45
+ @authorization_adapter = AuthorizationAdapter.new(engine: @fields_engine, user: @user)
46
+ @authorization_adapter.process
47
+ end
48
+
49
+ sub_query = summaryreq[:root_klass]
50
+ .from([
51
+ @fields_engine.froms,
52
+ @selections_engine.froms,
53
+ ].flatten.uniq)
54
+ .select(@fields_engine.selects)
55
+ .joins([
56
+ @selections_engine.ordered_joins,
57
+ @fields_engine.ordered_joins
58
+ ].flatten.compact)
59
+ .where([@selections_engine.wheres, @fields_engine.wheres].flatten.compact.reduce(&:and))
60
+ .as(summaryreq[:select_req][:field_name].to_s)
61
+
62
+ @selects << sub_query
63
+ end
64
+
65
+ end
66
+
67
+ def query
68
+ @root_klass
69
+ .select(@selects)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module ActionBlocks
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ActionBlocks
4
+ end
5
+ end
@@ -0,0 +1,62 @@
1
+ module ActionBlocks
2
+ # Exception class to raise when there is an authorized access
3
+ # exception thrown. The exception has a few goodies that may
4
+ # be useful for capturing / recognizing security issues.
5
+ class AccessDenied < StandardError
6
+ attr_reader :user, :action, :subject
7
+
8
+ def initialize(user, action, subject = nil)
9
+ @user, @action, @subject = user, action, subject
10
+
11
+ super()
12
+ end
13
+
14
+ def message
15
+ I18n.t("action_block.access_denied.message")
16
+ end
17
+ end
18
+
19
+ class Error < RuntimeError
20
+ end
21
+
22
+ class ErrorLoading < Error
23
+ # Locates the most recent file and line from the caught exception's backtrace.
24
+ def find_cause(folder, backtrace)
25
+ backtrace.grep(/\/(#{folder}\/.*\.rb):(\d+)/){ [$1, $2] }.first
26
+ end
27
+ end
28
+
29
+ class DatabaseHitDuringLoad < ErrorLoading
30
+ def initialize(exception)
31
+ file, line = find_cause(:app, exception.backtrace)
32
+
33
+ super "Your file, #{file} (line #{line}), caused a database error while Active Block was loading. This " +
34
+ "is most common when your database is missing or doesn't have the latest migrations applied. To " +
35
+ "prevent this error, move the code to a place where it will only be run when a page is rendered. " +
36
+ "One solution can be, to wrap the query in a Proc. " +
37
+ "Original error message: #{exception.message}"
38
+ end
39
+
40
+ def self.capture
41
+ yield
42
+ rescue *database_error_classes => exception
43
+ raise new exception
44
+ end
45
+
46
+ private
47
+
48
+ def self.database_error_classes
49
+ @classes ||= []
50
+ end
51
+ end
52
+
53
+ class DependencyError < ErrorLoading
54
+ end
55
+
56
+ class NoMenuError < KeyError
57
+ end
58
+
59
+ class GeneratorError < Error
60
+ end
61
+
62
+ end
@@ -0,0 +1,134 @@
1
+ module ActionBlocks
2
+ module GeneratorHelper
3
+ protected
4
+
5
+ def variable(cname=nil)
6
+ cn = cname || class_name
7
+ variableize(cn)
8
+ end
9
+
10
+ def collection_name(cname=nil)
11
+ cn = cname || class_name
12
+ collectionize(cn)
13
+ end
14
+
15
+ def collectionize(s)
16
+ s.to_s.underscore.pluralize
17
+ end
18
+
19
+ def variableize(s)
20
+ s.to_s.underscore.singularize
21
+ end
22
+
23
+ def has_many_associations?(cname=nil)
24
+ cn = cname || class_name
25
+ cn.constantize.reflect_on_all_associations(:has_many).map(&:name).length > 0
26
+ end
27
+
28
+ def has_many_associations(cname=nil)
29
+ cn = cname || class_name
30
+ cn.constantize.reflect_on_all_associations(:has_many).map(&:name).select {|hm| hm.downcase != 'versions'}
31
+ end
32
+
33
+ def has_many_association_details(cname=nil)
34
+ cn = cname || class_name
35
+ cn.constantize.reflect_on_all_associations(:has_many).select {|hm| hm.name.downcase != 'versions'}
36
+ end
37
+
38
+ def has_one_associations(cname=nil)
39
+ cn = cname || class_name
40
+ cn.constantize.reflect_on_all_associations(:has_one).map(&:name)
41
+ end
42
+
43
+ def has_one_association_details(cname=nil)
44
+ cn = cname || class_name
45
+ cn.constantize.reflect_on_all_associations(:has_one)
46
+ end
47
+
48
+ def belongs_to_associations(cname=nil)
49
+ if respond_to?(:attributes) && attributes.present?
50
+ attributes.select{|a| a.reference?}.map(&:name)
51
+ else
52
+ cn = cname || class_name
53
+ begin
54
+ cn.constantize.reflect_on_all_associations(:belongs_to).map(&:name)
55
+ rescue
56
+ []
57
+ end
58
+ end
59
+ end
60
+
61
+ def belongs_to_association_details(cname=nil)
62
+ if respond_to?(:attributes) && attributes.present?
63
+ attributes.select{|a| a.reference?}
64
+ else
65
+ cn = cname || class_name
66
+ begin
67
+ cn.constantize.reflect_on_all_associations(:belongs_to)
68
+ rescue
69
+ []
70
+ end
71
+ end
72
+ end
73
+
74
+ def content_columns(cname=nil)
75
+ if respond_to?(:attributes) && attributes.present?
76
+ cols = attributes.reject{|a| a.reference?}.map(&:name).map{|n| clean(n)}.compact
77
+ cols
78
+ else
79
+ cn = cname || class_name
80
+ begin
81
+ cols = cn.constantize.content_columns.map(&:name).map {|c| clean(c)}.compact
82
+ cols
83
+ rescue => ex
84
+ puts ex.message
85
+ []
86
+ end
87
+ end
88
+ end
89
+
90
+ def content_column_details(cname=nil)
91
+ if respond_to?(:attributes) && attributes.present?
92
+ attributes.reject{|a| a.reference?}
93
+ else
94
+ cn = cname || class_name
95
+ begin
96
+ cn.constantize.content_columns
97
+ rescue
98
+ []
99
+ end
100
+ end
101
+ end
102
+
103
+ def clean(c)
104
+ c = c.to_s
105
+ return nil if c.in?(%w(updated_at created_at deleted deleted_at)) || c =~ /_file_size|_updated_at|_content_type/
106
+ c.to_s.gsub('_file_name','')
107
+ end
108
+
109
+ def attachment_names(cname=nil)
110
+ if respond_to?(:attributes) && attributes.present?
111
+ attributes.select{|a| a.attachment?}.map(&:name)
112
+ else
113
+ cn = cname || class_name
114
+ names = cn.constantize.content_columns.select{|a| a.name.to_s =~ /_file_name\Z/}.map{|a| a.name.to_s.gsub(/_file_name\Z/, "")}
115
+ names
116
+ end
117
+ end
118
+
119
+ def association_class_exists_with_this_name?(association_name)
120
+ klass = Module.const_get(association_name.to_s.singularize.classify)
121
+ return klass.is_a?(Class)
122
+ rescue NameError
123
+ return false
124
+ end
125
+
126
+ def field_content_columns
127
+ if options.fields && options.fields.length > 0
128
+ return options.fields & content_columns
129
+ else
130
+ return content_columns
131
+ end
132
+ end
133
+ end
134
+ end