has_dynamic_columns 0.2.1 → 0.3.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -3
  3. data/README.md +25 -1
  4. data/has_dynamic_columns.gemspec +3 -1
  5. data/lib/generators/has_dynamic_columns/active_record_generator.rb +1 -1
  6. data/lib/generators/has_dynamic_columns/templates/migration.rb +4 -4
  7. data/lib/generators/has_dynamic_columns/templates/migration_0.3.0.rb +89 -0
  8. data/lib/generators/has_dynamic_columns/upgrade_0_3_0_active_record_generator.rb +22 -0
  9. data/lib/has_dynamic_columns/active_record/query_methods.rb +1 -167
  10. data/lib/has_dynamic_columns/active_record/relation.rb +1 -21
  11. data/lib/has_dynamic_columns/active_record/v3/query_methods.rb +281 -0
  12. data/lib/has_dynamic_columns/active_record/v3/relation.rb +34 -0
  13. data/lib/has_dynamic_columns/active_record/v4/query_methods.rb +257 -0
  14. data/lib/has_dynamic_columns/active_record/v4/relation.rb +34 -0
  15. data/lib/has_dynamic_columns/dynamic_column_boolean_datum.rb +5 -0
  16. data/lib/has_dynamic_columns/dynamic_column_date_datum.rb +5 -0
  17. data/lib/has_dynamic_columns/dynamic_column_datetime_datum.rb +5 -0
  18. data/lib/has_dynamic_columns/dynamic_column_datum.rb +8 -54
  19. data/lib/has_dynamic_columns/dynamic_column_enum_datum.rb +5 -0
  20. data/lib/has_dynamic_columns/dynamic_column_float_datum.rb +5 -0
  21. data/lib/has_dynamic_columns/dynamic_column_integer_datum.rb +5 -0
  22. data/lib/has_dynamic_columns/dynamic_column_string_datum.rb +5 -0
  23. data/lib/has_dynamic_columns/dynamic_column_text_datum.rb +5 -0
  24. data/lib/has_dynamic_columns/dynamic_column_time_datum.rb +5 -0
  25. data/lib/has_dynamic_columns/dynamic_column_timestamp_datum.rb +5 -0
  26. data/lib/has_dynamic_columns/model/class_methods.rb +22 -2
  27. data/lib/has_dynamic_columns/version.rb +1 -1
  28. data/lib/has_dynamic_columns.rb +11 -0
  29. data/rspec_rvm +39 -0
  30. data/spec/factories/account.rb +24 -0
  31. data/spec/factories/customer.rb +35 -0
  32. data/spec/has_dynamic_columns/active_record/query_methods_spec.rb +252 -0
  33. data/spec/has_dynamic_columns/dynamic_columns_integer_datum_spec.rb +124 -0
  34. data/spec/has_dynamic_columns/dynamic_columns_string_datum_spec.rb +7 -0
  35. data/spec/has_dynamic_columns_spec.rb +93 -63
  36. data/spec/spec_helper.rb +13 -8
  37. metadata +67 -6
@@ -0,0 +1,281 @@
1
+ module HasDynamicColumns
2
+ module ActiveRecord
3
+ module QueryMethods
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :order, :dynamic_columns
7
+ alias_method_chain :where, :dynamic_columns
8
+ alias_method_chain :build_arel, :dynamic_columns
9
+
10
+ def preprocess_order_args(order_args)
11
+ order_args.flatten!
12
+ #validate_order_args(order_args)
13
+
14
+ references = order_args.grep(String)
15
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
16
+ references!(references) if references.any?
17
+
18
+ # if a symbol is given we prepend the quoted table name
19
+ order_args.map! do |arg|
20
+ case arg
21
+ when Symbol
22
+ #arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
23
+ table[arg].asc
24
+ when Hash
25
+ arg.map { |field, dir|
26
+ #field = klass.attribute_alias(field) if klass.attribute_alias?(field)
27
+ table[field].send(dir.downcase)
28
+ }
29
+ else
30
+ arg
31
+ end
32
+ end.flatten!
33
+ end
34
+
35
+
36
+ def dynamic_column_process_arel_nodes(rel, scope, index, joins)
37
+ case rel
38
+ when Arel::Nodes::Grouping
39
+ dynamic_column_process_arel_nodes(rel.expr, scope, index+1, joins)
40
+ when Arel::Nodes::Or
41
+ dynamic_column_process_arel_nodes(rel.left, scope, index+1, joins)
42
+ dynamic_column_process_arel_nodes(rel.right, scope, index+10000, joins) # Hack - queries with over 10,000 dynamic where conditions may break
43
+ when Arel::Nodes::And
44
+ dynamic_column_process_arel_nodes(rel.left, scope, index+1, joins)
45
+ dynamic_column_process_arel_nodes(rel.right, scope, index+10000, joins) # Hack - queries with over 10,000 dynamic where conditions may break
46
+ # We can work with this
47
+ when Arel::Nodes::Descending
48
+ col_name = rel.expr.name
49
+ dynamic_type = rel.expr.relation.engine
50
+
51
+ rel.expr.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table]
52
+ rel.expr.name = :value
53
+ # We can work with this
54
+ when Arel::Nodes::Ascending
55
+ col_name = rel.expr.name
56
+ dynamic_type = rel.expr.relation.engine
57
+
58
+ rel.expr.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table]
59
+ rel.expr.name = :value
60
+ # We can work with this
61
+ else
62
+ col_name = rel.left.name
63
+ dynamic_type = rel.left.relation.engine.to_s
64
+
65
+ rel.left.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table] # modify the where to use the aliased table
66
+ rel.left.name = :value # value is the data storage column searchable on dynamic_column_data table
67
+ end
68
+ end
69
+
70
+ # Builds the joins required for this dynamic column
71
+ # Modifies the where to use the dynamic_column_data table alias
72
+ #
73
+ # rel - an arel node
74
+ # scope - scope to run the conditions in
75
+ # index - unique table identifier
76
+ # joins - list of joins processed (to prevent duplicates)
77
+ def dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index, joins)
78
+ field_scope = scope
79
+ field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
80
+ field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
81
+
82
+ joins_scope_key = "#{field_scope_type}_#{field_scope_id}"
83
+ joins[joins_scope_key] ||= {}
84
+
85
+ column_datum_store_table_type = "HasDynamicColumns::DynamicColumnStringDatum"
86
+ if !field_scope.nil? && a = field_scope.activerecord_dynamic_columns.where(key: col_name).first
87
+ column_datum_store_table_type = "HasDynamicColumns::DynamicColumn#{a.data_type.to_s.capitalize}Datum"
88
+ end
89
+
90
+ column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{index}_#{col_name}")
91
+ column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{index}_#{col_name}")
92
+ column_datum_store_table = column_datum_store_table_type.constantize.arel_table.alias("dynamic_where_data_store_#{index}_#{col_name}")
93
+
94
+ # Join for this scope/col already added - continue
95
+ if !joins[joins_scope_key][col_name].nil?
96
+ return joins[joins_scope_key][col_name]
97
+ end
98
+
99
+ # Join on the column with the key
100
+ on_query = column_table[:key].eq(col_name)
101
+ on_query = on_query.and(
102
+ column_table[:field_scope_type].eq(field_scope_type)
103
+ ) unless field_scope_type.nil?
104
+
105
+ on_query = on_query.and(
106
+ column_table[:field_scope_id].eq(field_scope_id)
107
+ ) unless field_scope_id.nil?
108
+
109
+ column_table_join_on = column_table
110
+ .create_on(
111
+ on_query
112
+ )
113
+
114
+ column_table_join = table.create_join(column_table, column_table_join_on)
115
+ self.joins_values += [column_table_join]
116
+
117
+ # Join on all the data with the provided key
118
+ column_table_datum_join_on = column_datum_table
119
+ .create_on(
120
+ column_datum_table[:owner_id].eq(table[:id]).and(
121
+ column_datum_table[:owner_type].eq(dynamic_type.to_s)
122
+ ).and(
123
+ column_datum_table[:dynamic_column_id].eq(column_table[:id])
124
+ )
125
+ )
126
+
127
+ column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on, Arel::Nodes::OuterJoin)
128
+ self.joins_values += [column_table_datum_join]
129
+
130
+ # Join on the actual data
131
+ column_table_datum_store_join_on = column_datum_store_table
132
+ .create_on(
133
+ column_datum_table[:datum_id].eq(column_datum_store_table[:id]).and(
134
+ column_datum_table[:datum_type].eq(column_datum_store_table_type)
135
+ )
136
+ )
137
+
138
+ column_table_datum_store_join = table.create_join(column_datum_store_table, column_table_datum_store_join_on, Arel::Nodes::OuterJoin)
139
+ self.joins_values += [column_table_datum_store_join]
140
+
141
+ joins[joins_scope_key][col_name] = {
142
+ :join => column_table_datum_store_join,
143
+ :table => column_datum_store_table
144
+ }
145
+
146
+ joins[joins_scope_key][col_name]
147
+ end
148
+
149
+ # Builds all the joins required for the dynamic columns in the where/order clauses
150
+ def build_dynamic_column_joins
151
+ joins = {}
152
+
153
+ self.where_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
154
+ dynamic_scope[:where].each_with_index { |rel, index_inner|
155
+ # Process each where
156
+ dynamic_column_process_arel_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000), joins)
157
+
158
+ # Warning
159
+ # Must cast rel to a string - I've encountered ***strange*** situations where this will change the 'col_name' to value in the where clause
160
+ # specifically, the test 'case should restrict if scope specified' will fail
161
+ self.where_values += [rel.to_sql]
162
+ }
163
+ }
164
+ self.order_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
165
+ dynamic_scope[:order].each_with_index { |rel, index_inner|
166
+ # Process each order
167
+ dynamic_column_process_arel_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000), joins)
168
+ }
169
+ }
170
+
171
+ true
172
+ end
173
+ end
174
+ end
175
+
176
+ # When arel starts building - filter
177
+ def build_arel_with_dynamic_columns
178
+ self.build_dynamic_column_joins
179
+
180
+ if self.where_dynamic_columns_values.length > 0 || self.order_dynamic_columns_values.length > 0
181
+ self.group_values += [Arel::Nodes::Group.new(table[:id])]
182
+ end
183
+
184
+ build_arel_without_dynamic_columns
185
+ end
186
+
187
+ # lifted from
188
+ # http://erniemiller.org/2013/10/07/activerecord-where-not-sane-true/
189
+ class WhereChain
190
+ def initialize(scope)
191
+ @scope = scope
192
+ end
193
+
194
+ # Extends where to chain a has_dynamic_columns method
195
+ # This builds all the joins needed to search the has_dynamic_columns_data tables
196
+ def has_dynamic_columns(opts = :chain, *rest)
197
+ # Map
198
+ dynamic_columns_value = {
199
+ :scope => nil,
200
+ :where => @scope.send(:build_where, opts, rest)
201
+ }
202
+ @scope.where_dynamic_columns_values = dynamic_columns_value
203
+
204
+ chain = ::ActiveRecord::QueryMethods::WhereChain.new(@scope)
205
+ chain.instance_eval do
206
+ # Make outer scope variable accessible
207
+ @dynamic_columns_value = dynamic_columns_value
208
+
209
+ # Extends where to chain with a has_scope method
210
+ # This scopes the where from above
211
+ def with_scope(opt)
212
+ @dynamic_columns_value[:scope] = opt
213
+
214
+ @scope
215
+ end
216
+ def without_scope
217
+ @scope
218
+ end
219
+ end
220
+
221
+ chain
222
+ end
223
+ end
224
+
225
+ def where_with_dynamic_columns(opts = :chain, *rest)
226
+ if opts == :chain
227
+ ::ActiveRecord::QueryMethods::WhereChain.new(clone)
228
+ else
229
+ where_without_dynamic_columns(opts, rest)
230
+ end
231
+ end
232
+
233
+ # OrderChain objects act as placeholder for queries in which #order does not have any parameter.
234
+ # In this case, #order must be chained with #by_dynamic_columns to return a new relation.
235
+ class OrderChain
236
+ def initialize(scope)
237
+ @scope = scope
238
+ end
239
+
240
+ def by_dynamic_columns(*args)
241
+ @scope.send(:preprocess_order_args, args)
242
+
243
+ # Add now - want to keep the order with the regular column orders
244
+ @scope.order_values += args
245
+
246
+ # Map
247
+ dynamic_columns_value = {
248
+ :scope => nil,
249
+ :order => args,
250
+ }
251
+ @scope.order_dynamic_columns_values = dynamic_columns_value
252
+
253
+ chain = ::ActiveRecord::QueryMethods::OrderChain.new(@scope)
254
+ chain.instance_eval do
255
+ # Make outer scope variable accessible
256
+ @dynamic_columns_value = dynamic_columns_value
257
+
258
+ # Extends where to chain with a has_scope method
259
+ # This scopes the where from above
260
+ def with_scope(opt)
261
+ @dynamic_columns_value[:scope] = opt
262
+
263
+ @scope
264
+ end
265
+ end
266
+
267
+ chain
268
+ end
269
+ end
270
+
271
+ def order_with_dynamic_columns(opts = :chain)
272
+ # Chain - by_dynamic_columns
273
+ if opts == :chain
274
+ ::ActiveRecord::QueryMethods::OrderChain.new(clone)
275
+ else
276
+ order_without_dynamic_columns(opts)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,34 @@
1
+ module HasDynamicColumns
2
+ module ActiveRecord
3
+ module Relation
4
+ def self.included(base)
5
+ base.class_eval do
6
+ attr_accessor :where_dynamic_columns_values, :order_dynamic_columns_values
7
+
8
+ # Collect all where clauses
9
+ def where_dynamic_columns_values
10
+ @where_dynamic_columns_values || []
11
+ end
12
+ def where_dynamic_columns_values=values
13
+ raise ImmutableRelation if @loaded
14
+ @where_dynamic_columns_values ||= []
15
+ @where_dynamic_columns_values << values
16
+ end
17
+
18
+ # Collect all order clauses
19
+ def order_dynamic_columns_values
20
+ @order_dynamic_columns_values || []
21
+ end
22
+ def order_dynamic_columns_values=values
23
+ raise ImmutableRelation if @loaded
24
+ @order_dynamic_columns_values ||= []
25
+ @order_dynamic_columns_values << values
26
+ end
27
+ end
28
+
29
+ base.instance_eval do
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,257 @@
1
+ module HasDynamicColumns
2
+ module ActiveRecord
3
+ module QueryMethods
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :order, :dynamic_columns
7
+ alias_method_chain :where, :dynamic_columns
8
+ alias_method_chain :build_arel, :dynamic_columns
9
+
10
+ def dynamic_column_process_arel_nodes(rel, scope, index, joins)
11
+ case rel
12
+ when Arel::Nodes::Grouping
13
+ dynamic_column_process_arel_nodes(rel.expr, scope, index+1, joins)
14
+ when Arel::Nodes::Or
15
+ dynamic_column_process_arel_nodes(rel.left, scope, index+1, joins)
16
+ dynamic_column_process_arel_nodes(rel.right, scope, index+10000, joins) # Hack - queries with over 10,000 dynamic where conditions may break
17
+ when Arel::Nodes::And
18
+ dynamic_column_process_arel_nodes(rel.left, scope, index+1, joins)
19
+ dynamic_column_process_arel_nodes(rel.right, scope, index+10000, joins) # Hack - queries with over 10,000 dynamic where conditions may break
20
+ # We can work with this
21
+ when Arel::Nodes::Descending
22
+ col_name = rel.expr.name
23
+ dynamic_type = rel.expr.relation.engine
24
+
25
+ rel.expr.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table]
26
+ rel.expr.name = :value
27
+ # We can work with this
28
+ when Arel::Nodes::Ascending
29
+ col_name = rel.expr.name
30
+ dynamic_type = rel.expr.relation.engine
31
+
32
+ rel.expr.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table]
33
+ rel.expr.name = :value
34
+ # We can work with this
35
+ else
36
+ col_name = rel.left.name
37
+ dynamic_type = rel.left.relation.engine.to_s
38
+
39
+ rel.left.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table] # modify the where to use the aliased table
40
+ rel.left.name = :value # value is the data storage column searchable on dynamic_column_data table
41
+ end
42
+ end
43
+
44
+ # Builds the joins required for this dynamic column
45
+ # Modifies the where to use the dynamic_column_data table alias
46
+ #
47
+ # rel - an arel node
48
+ # scope - scope to run the conditions in
49
+ # index - unique table identifier
50
+ # joins - list of joins processed (to prevent duplicates)
51
+ def dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index, joins)
52
+ field_scope = scope
53
+ field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
54
+ field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
55
+
56
+ joins_scope_key = "#{field_scope_type}_#{field_scope_id}"
57
+ joins[joins_scope_key] ||= {}
58
+
59
+ column_datum_store_table_type = "HasDynamicColumns::DynamicColumnStringDatum"
60
+ if !field_scope.nil? && a = field_scope.activerecord_dynamic_columns.where(key: col_name).first
61
+ column_datum_store_table_type = "HasDynamicColumns::DynamicColumn#{a.data_type.to_s.capitalize}Datum"
62
+ end
63
+
64
+ column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{index}_#{col_name}")
65
+ column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{index}_#{col_name}")
66
+ column_datum_store_table = column_datum_store_table_type.constantize.arel_table.alias("dynamic_where_data_store_#{index}_#{col_name}")
67
+
68
+ # Join for this scope/col already added - continue
69
+ if !joins[joins_scope_key][col_name].nil?
70
+ return joins[joins_scope_key][col_name]
71
+ end
72
+
73
+ # Join on the column with the key
74
+ on_query = column_table[:key].eq(col_name)
75
+ on_query = on_query.and(
76
+ column_table[:field_scope_type].eq(field_scope_type)
77
+ ) unless field_scope_type.nil?
78
+
79
+ on_query = on_query.and(
80
+ column_table[:field_scope_id].eq(field_scope_id)
81
+ ) unless field_scope_id.nil?
82
+
83
+ column_table_join_on = column_table
84
+ .create_on(
85
+ on_query
86
+ )
87
+
88
+ column_table_join = table.create_join(column_table, column_table_join_on)
89
+ self.joins_values += [column_table_join]
90
+
91
+ # Join on all the data with the provided key
92
+ column_table_datum_join_on = column_datum_table
93
+ .create_on(
94
+ column_datum_table[:owner_id].eq(table[:id]).and(
95
+ column_datum_table[:owner_type].eq(dynamic_type)
96
+ ).and(
97
+ column_datum_table[:dynamic_column_id].eq(column_table[:id])
98
+ )
99
+ )
100
+
101
+ column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on, Arel::Nodes::OuterJoin)
102
+ self.joins_values += [column_table_datum_join]
103
+
104
+ # Join on the actual data
105
+ column_table_datum_store_join_on = column_datum_store_table
106
+ .create_on(
107
+ column_datum_table[:datum_id].eq(column_datum_store_table[:id]).and(
108
+ column_datum_table[:datum_type].eq(column_datum_store_table_type)
109
+ )
110
+ )
111
+
112
+ column_table_datum_store_join = table.create_join(column_datum_store_table, column_table_datum_store_join_on, Arel::Nodes::OuterJoin)
113
+ self.joins_values += [column_table_datum_store_join]
114
+
115
+ joins[joins_scope_key][col_name] = {
116
+ :join => column_table_datum_store_join,
117
+ :table => column_datum_store_table
118
+ }
119
+
120
+ joins[joins_scope_key][col_name]
121
+ end
122
+
123
+ # Builds all the joins required for the dynamic columns in the where/order clauses
124
+ def build_dynamic_column_joins
125
+ joins = {}
126
+
127
+ self.where_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
128
+ dynamic_scope[:where].each_with_index { |rel, index_inner|
129
+ # Process each where
130
+ dynamic_column_process_arel_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000), joins)
131
+
132
+ # Warning
133
+ # Must cast rel to a string - I've encountered ***strange*** situations where this will change the 'col_name' to value in the where clause
134
+ # specifically, the test 'case should restrict if scope specified' will fail
135
+ self.where_values += [rel.to_sql]
136
+ }
137
+ }
138
+ self.order_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
139
+ dynamic_scope[:order].each_with_index { |rel, index_inner|
140
+ # Process each order
141
+ dynamic_column_process_arel_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000), joins)
142
+ }
143
+ }
144
+
145
+ true
146
+ end
147
+ end
148
+ end
149
+
150
+ # When arel starts building - filter
151
+ def build_arel_with_dynamic_columns
152
+ self.build_dynamic_column_joins
153
+
154
+ if self.where_dynamic_columns_values.length > 0 || self.order_dynamic_columns_values.length > 0
155
+ self.group_values += [Arel::Nodes::Group.new(table[:id])]
156
+ end
157
+
158
+ build_arel_without_dynamic_columns
159
+ end
160
+
161
+ # lifted from
162
+ # http://erniemiller.org/2013/10/07/activerecord-where-not-sane-true/
163
+ module WhereChainCompatibility
164
+ #include ::ActiveRecord::QueryMethods
165
+ #define_method :build_where,
166
+ # ::ActiveRecord::QueryMethods.instance_method(:build_where)
167
+
168
+ # Extends where to chain a has_dynamic_columns method
169
+ # This builds all the joins needed to search the has_dynamic_columns_data tables
170
+ def has_dynamic_columns(opts = :chain, *rest)
171
+ # Map
172
+ dynamic_columns_value = {
173
+ :scope => nil,
174
+ :where => @scope.send(:build_where, opts, rest)
175
+ }
176
+ @scope.where_dynamic_columns_values = dynamic_columns_value
177
+
178
+ chain = ::ActiveRecord::QueryMethods::WhereChain.new(@scope)
179
+ chain.instance_eval do
180
+ # Make outer scope variable accessible
181
+ @dynamic_columns_value = dynamic_columns_value
182
+
183
+ # Extends where to chain with a has_scope method
184
+ # This scopes the where from above
185
+ def with_scope(opt)
186
+ @dynamic_columns_value[:scope] = opt
187
+
188
+ @scope
189
+ end
190
+ def without_scope
191
+ @scope
192
+ end
193
+ end
194
+
195
+ chain
196
+ end
197
+ end
198
+
199
+ def where_with_dynamic_columns(opts = :chain, *rest)
200
+ if opts == :chain
201
+ scope = spawn
202
+ chain = ::ActiveRecord::QueryMethods::WhereChain.new(scope)
203
+ chain.send(:extend, WhereChainCompatibility)
204
+ else
205
+ where_without_dynamic_columns(opts, rest)
206
+ end
207
+ end
208
+
209
+ # OrderChain objects act as placeholder for queries in which #order does not have any parameter.
210
+ # In this case, #order must be chained with #by_dynamic_columns to return a new relation.
211
+ class OrderChain
212
+ def initialize(scope)
213
+ @scope = scope
214
+ end
215
+
216
+ def by_dynamic_columns(*args)
217
+ @scope.send(:preprocess_order_args, args)
218
+
219
+ # Add now - want to keep the order with the regular column orders
220
+ @scope.order_values += args
221
+
222
+ # Map
223
+ dynamic_columns_value = {
224
+ :scope => nil,
225
+ :order => args,
226
+ }
227
+ @scope.order_dynamic_columns_values = dynamic_columns_value
228
+
229
+ chain = ::ActiveRecord::QueryMethods::OrderChain.new(@scope)
230
+ chain.instance_eval do
231
+ # Make outer scope variable accessible
232
+ @dynamic_columns_value = dynamic_columns_value
233
+
234
+ # Extends where to chain with a has_scope method
235
+ # This scopes the where from above
236
+ def with_scope(opt)
237
+ @dynamic_columns_value[:scope] = opt
238
+
239
+ @scope
240
+ end
241
+ end
242
+
243
+ chain
244
+ end
245
+ end
246
+
247
+ def order_with_dynamic_columns(opts = :chain)
248
+ # Chain - by_dynamic_columns
249
+ if opts == :chain
250
+ ::ActiveRecord::QueryMethods::OrderChain.new(spawn)
251
+ else
252
+ order_without_dynamic_columns(opts)
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,34 @@
1
+ ActiveRecord::VERSION::MAJOR
2
+
3
+ module HasDynamicColumns
4
+ module ActiveRecord
5
+ module Relation
6
+ def self.included(base)
7
+ base.class_eval do
8
+ # Collect all where clauses
9
+ def where_dynamic_columns_values
10
+ @values[:where_dynamic_columns_values] || []
11
+ end
12
+ def where_dynamic_columns_values=values
13
+ raise ImmutableRelation if @loaded
14
+ @values[:where_dynamic_columns_values] ||= []
15
+ @values[:where_dynamic_columns_values] << values
16
+ end
17
+
18
+ # Collect all order clauses
19
+ def order_dynamic_columns_values
20
+ @values[:order_dynamic_columns_values] || []
21
+ end
22
+ def order_dynamic_columns_values=values
23
+ raise ImmutableRelation if @loaded
24
+ @values[:order_dynamic_columns_values] ||= []
25
+ @values[:order_dynamic_columns_values] << values
26
+ end
27
+ end
28
+
29
+ base.instance_eval do
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnBooleanDatum < ::ActiveRecord::Base
3
+ belongs_to :dynamic_column_datum, :class_name => "HasDynamicColumns::DynamicColumnDatum"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnDateDatum < ::ActiveRecord::Base
3
+ belongs_to :dynamic_column_datum, :class_name => "HasDynamicColumns::DynamicColumnDatum"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnDatetimeDatum < ::ActiveRecord::Base
3
+ belongs_to :dynamic_column_datum, :class_name => "HasDynamicColumns::DynamicColumnDatum"
4
+ end
5
+ end
@@ -4,62 +4,16 @@ module HasDynamicColumns
4
4
  belongs_to :dynamic_column_option, :class_name => "HasDynamicColumns::DynamicColumnOption"
5
5
  belongs_to :owner, :polymorphic => true
6
6
 
7
- # Get value based on dynamic_column data_type
8
- def value
9
- if self.dynamic_column
10
- case self.dynamic_column.data_type
11
- when "list"
12
- if self.dynamic_column_option
13
- self.dynamic_column_option.key
14
- end
15
- when "datetime"
16
- self[:value]
17
- when "boolean"
18
-
19
- if self[:value].is_a?(TrueClass) || self[:value].is_a?(FalseClass)
20
- self[:value]
21
- else
22
- self[:value].to_i === 1
23
- end
24
- when "integer"
25
- self[:value]
26
- when "date"
27
- self[:value]
28
- when "string"
29
- self[:value]
30
- end
31
- else
32
- self[:value]
33
- end
34
- end
7
+ belongs_to :datum, :polymorphic => true
35
8
 
36
- # Set value base don dynamic_column data_type
37
9
  def value=v
38
- if self.dynamic_column
39
- case self.dynamic_column.data_type
40
- when "list"
41
- # Can only set the value to one of the option values
42
- if option = self.dynamic_column.dynamic_column_options.select { |i| i.key == v }.first
43
- self.dynamic_column_option = option
44
- else
45
- # Hacky, -1 indicates to the validator that an invalid option was set
46
- self.dynamic_column_option = nil
47
- self.dynamic_column_option_id = (v.to_s.length > 0)? -1 : nil
48
- end
49
- when "datetime"
50
- self[:value] = v
51
- when "boolean"
52
- self[:value] = (v)? 1 : 0
53
- when "integer"
54
- self[:value] = v
55
- when "date"
56
- self[:value] = v
57
- when "string"
58
- self[:value] = v
59
- end
60
- else
61
- self[:value] = v
62
- end
10
+ data_type = "string"
11
+ data_type = self.dynamic_column.data_type if self.dynamic_column
12
+
13
+ self.datum = "::HasDynamicColumns::DynamicColumn#{data_type.capitalize}Datum".constantize.new(value: v)
14
+ end
15
+ def value
16
+ self.datum.value if self.datum
63
17
  end
64
18
  end
65
19
  end