has_dynamic_columns 0.2.1 → 0.3.2

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