has_dynamic_columns 0.3.3 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f728b40892f557858d9809deb92fe4f6c80e54a
4
- data.tar.gz: f8c151d8a22042bfb7803be46dce5aefd06600a4
3
+ metadata.gz: 654ea8aee466792c91ae8278022ad10ff034c072
4
+ data.tar.gz: 7f5f8b41a767bd2fc12ab09b4d5c3e8df57e5e96
5
5
  SHA512:
6
- metadata.gz: 7a73f110f7ea4dd8e4509e9cb62b53016976e4cdf98286511e5ccd8fa09fe939b1535e829ef561179fc46c87d36fc084c53abf2bdc1fa6cbba19c5aa4977e382
7
- data.tar.gz: a186cb3ab5e69896847bb6e91a8f291435680547d1013fae4c76b189642f12714f0cd7c95cfacb893bf81d460d4fd871623733b98b1d1db8039cf695756b2ddf
6
+ metadata.gz: 76a5cf1aa4b44b2ba1e6a02b40150ef23222015d3c812e3cb3061b44ceda271f5ebe9296cb2d5f19d44c1ee7ed064c0c90beacefa56903d256b2217969f19c69
7
+ data.tar.gz: f2adf795d3b55a202237b5edbfb8f780ceb79d2011d19bb9916c16dc8a0edb389c2fc01d31d35e7c924523267900038e4a3c3aee2160b8626c69c6cdfb1fc9f3
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
4
+ - 2.0.0
5
+ - 1.9.3
6
+ script: rspec spec
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![Gem Version](https://badge.fury.io/rb/has_dynamic_columns.svg)](http://badge.fury.io/rb/has_dynamic_columns)
2
+ [![Build Status](https://travis-ci.org/butchmarshall/has_dynamic_columns.svg?branch=master)](https://travis-ci.org/butchmarshall/has_dynamic_columns)
3
+
1
4
  has_dynamic_columns
2
5
  ============
3
6
 
@@ -6,6 +9,13 @@ This plugin gives ActiveRecord models the ability to dynamically define collecta
6
9
  Release Notes
7
10
  ============
8
11
 
12
+ **0.3.5**
13
+ - Added model storage type (for storing association with other activerecord objects)
14
+
15
+ **0.3.4**
16
+ - Can now store array of data
17
+ - JRuby support
18
+
9
19
  **0.3.0**
10
20
  - Moved to storing data types in separate tables (where/order now correct!)
11
21
  - Added order.by_dynamic_columns
@@ -24,6 +34,7 @@ running the following command:
24
34
 
25
35
  rails generate has_dynamic_columns:active_record
26
36
  rails generate has_dynamic_columns:upgrade_0_3_0_active_record
37
+ rails generate has_dynamic_columns:upgrade_0_3_4_active_record
27
38
  rake db:migrate
28
39
 
29
40
  Usage
@@ -19,9 +19,13 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "activerecord", [">= 3.0", "< 5.0"]
22
-
23
- spec.add_development_dependency "sqlite3", "> 0"
24
- spec.add_development_dependency "factory_girl", "> 0"
22
+ if RUBY_PLATFORM == 'java'
23
+ spec.add_development_dependency "jdbc-sqlite3", "> 0"
24
+ spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "> 0"
25
+ else
26
+ spec.add_development_dependency "sqlite3", "> 0"
27
+ end
28
+ spec.add_development_dependency "factory_girl", "> 4.0"
25
29
  spec.add_development_dependency "bundler", "~> 1.7"
26
30
  spec.add_development_dependency "rake", "~> 10.0"
27
31
  end
@@ -0,0 +1,8 @@
1
+ class Upgrade034HasDynamicColumns < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :dynamic_columns, :multiple, :boolean, :default => false
4
+ end
5
+
6
+ def self.down
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ class Upgrade035HasDynamicColumns < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :dynamic_column_model_data do |t|
4
+ t.integer :value_id
5
+ t.string :value_type
6
+ t.timestamps null: false
7
+ end
8
+ add_index(:dynamic_column_model_data, [:value_id,:value_type], name: "index_by_value")
9
+ add_column :dynamic_columns, :class_name, :string
10
+ add_column :dynamic_columns, :column_name, :string
11
+ end
12
+
13
+ def self.down
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ require "generators/has_dynamic_columns/has_dynamic_columns_generator"
2
+ require "generators/has_dynamic_columns/next_migration_version"
3
+ require "rails/generators/migration"
4
+ require "rails/generators/active_record"
5
+
6
+ # Extend the HasDynamicColumnsGenerator so that it creates an AR migration
7
+ module HasDynamicColumns
8
+ class Upgrade_0_3_4_ActiveRecordGenerator < ::HasDynamicColumnsGenerator
9
+ include Rails::Generators::Migration
10
+ extend NextMigrationVersion
11
+
12
+ source_paths << File.join(File.dirname(__FILE__), "templates")
13
+
14
+ def create_migration_file
15
+ migration_template "migration_0.3.4.rb", "db/migrate/upgrade_0_3_4_has_dynamic_columns.rb"
16
+ end
17
+
18
+ def self.next_migration_number(dirname)
19
+ ::ActiveRecord::Generators::Base.next_migration_number dirname
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require "generators/has_dynamic_columns/has_dynamic_columns_generator"
2
+ require "generators/has_dynamic_columns/next_migration_version"
3
+ require "rails/generators/migration"
4
+ require "rails/generators/active_record"
5
+
6
+ # Extend the HasDynamicColumnsGenerator so that it creates an AR migration
7
+ module HasDynamicColumns
8
+ class Upgrade_0_3_5_ActiveRecordGenerator < ::HasDynamicColumnsGenerator
9
+ include Rails::Generators::Migration
10
+ extend NextMigrationVersion
11
+
12
+ source_paths << File.join(File.dirname(__FILE__), "templates")
13
+
14
+ def create_migration_file
15
+ migration_template "migration_0.3.5.rb", "db/migrate/upgrade_0_3_5_has_dynamic_columns.rb"
16
+ end
17
+
18
+ def self.next_migration_number(dirname)
19
+ ::ActiveRecord::Generators::Base.next_migration_number dirname
20
+ end
21
+ end
22
+ end
@@ -1 +1,232 @@
1
- require_relative "v#{ActiveRecord::VERSION::MAJOR}/query_methods"
1
+ # Common modifications to ActiveRecord::QueryMethods
2
+ require_relative "v#{ActiveRecord::VERSION::MAJOR}/query_methods"
3
+
4
+ module HasDynamicColumns
5
+ module ActiveRecord
6
+ module QueryMethods
7
+ def self.included(base)
8
+ base.class_eval do
9
+ include ClassMethods
10
+
11
+ alias_method_chain :order, :dynamic_columns
12
+ alias_method_chain :where, :dynamic_columns
13
+ alias_method_chain :build_arel, :dynamic_columns
14
+
15
+ def dynamic_column_process_arel_nodes(rel, scope, index, joins)
16
+ case rel
17
+ when Arel::Nodes::Grouping
18
+ dynamic_column_process_arel_nodes(rel.expr, scope, index+1, joins)
19
+ when Arel::Nodes::Or
20
+ dynamic_column_process_arel_nodes(rel.left, scope, index+1, joins)
21
+ dynamic_column_process_arel_nodes(rel.right, scope, index+10000, joins) # Hack - queries with over 10,000 dynamic where conditions may break
22
+ when Arel::Nodes::And
23
+ dynamic_column_process_arel_nodes(rel.left, scope, index+1, joins)
24
+ dynamic_column_process_arel_nodes(rel.right, scope, index+10000, joins) # Hack - queries with over 10,000 dynamic where conditions may break
25
+ # We can work with this
26
+ when Arel::Nodes::Descending
27
+ col_name = rel.expr.name
28
+ dynamic_type = rel.expr.relation.engine
29
+
30
+ rel.expr.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table]
31
+ rel.expr.name = :value
32
+ # We can work with this
33
+ when Arel::Nodes::Ascending
34
+ col_name = rel.expr.name
35
+ dynamic_type = rel.expr.relation.engine
36
+
37
+ rel.expr.relation = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins)[:table]
38
+ rel.expr.name = :value
39
+ # We can work with this
40
+ else
41
+ col_name = rel.left.name
42
+ dynamic_type = rel.left.relation.engine.to_s
43
+
44
+ res = dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index+1, joins) # modify the where to use the aliased table
45
+
46
+ rel.left.relation = res[:table]
47
+ rel.left.name = res[:column] || :value # value is the data storage column searchable on dynamic_column_data table
48
+ end
49
+ end
50
+
51
+ # Builds the joins required for this dynamic column
52
+ # Modifies the where to use the dynamic_column_data table alias
53
+ #
54
+ # rel - an arel node
55
+ # scope - scope to run the conditions in
56
+ # index - unique table identifier
57
+ # joins - list of joins processed (to prevent duplicates)
58
+ def dynamic_column_build_arel_joins(col_name, dynamic_type, scope, index, joins)
59
+ field_scope = scope
60
+ field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
61
+ field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
62
+
63
+ joins_scope_key = "#{field_scope_type}_#{field_scope_id}"
64
+ joins[joins_scope_key] ||= {}
65
+
66
+ column_datum_store_table_type = "HasDynamicColumns::DynamicColumnStringDatum"
67
+ if !field_scope.nil? && a = field_scope.activerecord_dynamic_columns.where(key: col_name).first
68
+ column_datum_store_table_type = "HasDynamicColumns::DynamicColumn#{a.data_type.to_s.capitalize}Datum"
69
+ end
70
+
71
+ column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{index}_#{col_name}")
72
+ column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{index}_#{col_name}")
73
+ column_datum_store_table = column_datum_store_table_type.constantize.arel_table.alias("dynamic_where_data_store_#{index}_#{col_name}")
74
+
75
+ # Join for this scope/col already added - continue
76
+ if !joins[joins_scope_key][col_name].nil?
77
+ return joins[joins_scope_key][col_name]
78
+ end
79
+
80
+ # Join on the column with the key
81
+ on_query = column_table[:key].eq(col_name)
82
+ on_query = on_query.and(
83
+ column_table[:field_scope_type].eq(field_scope_type)
84
+ ) unless field_scope_type.nil?
85
+
86
+ on_query = on_query.and(
87
+ column_table[:field_scope_id].eq(field_scope_id)
88
+ ) unless field_scope_id.nil?
89
+
90
+ column_table_join_on = column_table
91
+ .create_on(
92
+ on_query
93
+ )
94
+
95
+ join_scope_type = (field_scope_id.nil?)? Arel::Nodes::OuterJoin : Arel::Nodes::InnerJoin
96
+
97
+ column_table_join = table.create_join(column_table, column_table_join_on)
98
+ self.joins_values += [column_table_join]
99
+
100
+ # Join on all the data with the provided key
101
+ column_table_datum_join_on = column_datum_table
102
+ .create_on(
103
+ column_datum_table[:owner_id].eq(table[:id]).and(
104
+ column_datum_table[:owner_type].eq(dynamic_type.to_s)
105
+ ).and(
106
+ column_datum_table[:dynamic_column_id].eq(column_table[:id])
107
+ )
108
+ )
109
+
110
+ column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on, join_scope_type)
111
+ self.joins_values += [column_table_datum_join]
112
+
113
+ # Join on the actual data
114
+ column_table_datum_store_join_on = column_datum_store_table
115
+ .create_on(
116
+ column_datum_table[:datum_id].eq(column_datum_store_table[:id]).and(
117
+ column_datum_table[:datum_type].eq(column_datum_store_table_type)
118
+ )
119
+ )
120
+
121
+ column_table_datum_store_join = table.create_join(column_datum_store_table, column_table_datum_store_join_on, join_scope_type)
122
+ self.joins_values += [column_table_datum_store_join]
123
+
124
+ column_name = :value
125
+
126
+ # If this dynamic column points to another model we need to join on that table
127
+ if !field_scope.nil? && assoc = field_scope.activerecord_dynamic_columns.where(data_type: "model", key: col_name).first
128
+ assoc_table = assoc.class_name.constantize.arel_table.alias("dynamic_where_associated_data_#{index}_#{col_name}")
129
+
130
+ join_on = assoc_table.create_on(
131
+ column_datum_store_table[:value_id].eq(assoc_table[:id]).and(
132
+ column_datum_store_table[:value_type].eq(assoc.class_name)
133
+ )
134
+ )
135
+ join = table.create_join(assoc_table, join_on)
136
+ self.joins_values += [join]
137
+
138
+ column_table_datum_store_join = join
139
+ column_datum_store_table = assoc_table
140
+ column_name = assoc.column_name.to_sym
141
+ end
142
+
143
+ # Track the joins
144
+ # - So they can be referenced later
145
+ # - So we don't make more joins than we have to
146
+ joins[joins_scope_key][col_name] = {
147
+ :join => column_table_datum_store_join,
148
+ :table => column_datum_store_table,
149
+ :column => column_name
150
+ }
151
+
152
+ joins[joins_scope_key][col_name]
153
+ end
154
+
155
+ # Builds all the joins required for the dynamic columns in the where/order clauses
156
+ def build_dynamic_column_joins
157
+ joins = {}
158
+
159
+ self.where_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
160
+ dynamic_scope[:where].each_with_index { |rel, index_inner|
161
+ # Process each where
162
+ dynamic_column_process_arel_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000), joins)
163
+
164
+ # Warning
165
+ # Must cast rel to a string - I've encountered ***strange*** situations where this will change the 'col_name' to value in the where clause
166
+ # specifically, the test 'case should restrict if scope specified' will fail
167
+ self.where_values += [rel.to_sql]
168
+ }
169
+ }
170
+ self.order_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
171
+ dynamic_scope[:order].each_with_index { |rel, index_inner|
172
+ # Process each order
173
+ dynamic_column_process_arel_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000), joins)
174
+ }
175
+ }
176
+
177
+ true
178
+ end
179
+ end
180
+ end
181
+
182
+ # When arel starts building - filter
183
+ def build_arel_with_dynamic_columns
184
+ self.build_dynamic_column_joins
185
+
186
+ if self.where_dynamic_columns_values.length > 0 || self.order_dynamic_columns_values.length > 0
187
+ self.group_values += [Arel::Nodes::Group.new(table[:id])]
188
+ end
189
+
190
+ build_arel_without_dynamic_columns
191
+ end
192
+
193
+ # OrderChain objects act as placeholder for queries in which #order does not have any parameter.
194
+ # In this case, #order must be chained with #by_dynamic_columns to return a new relation.
195
+ class OrderChain
196
+ def initialize(scope)
197
+ @scope = scope
198
+ end
199
+
200
+ def by_dynamic_columns(*args)
201
+ @scope.send(:preprocess_order_args, args)
202
+
203
+ # Add now - want to keep the order with the regular column orders
204
+ @scope.order_values += args
205
+
206
+ # Map
207
+ dynamic_columns_value = {
208
+ :scope => nil,
209
+ :order => args,
210
+ }
211
+ @scope.order_dynamic_columns_values = dynamic_columns_value
212
+
213
+ chain = ::ActiveRecord::QueryMethods::OrderChain.new(@scope)
214
+ chain.instance_eval do
215
+ # Make outer scope variable accessible
216
+ @dynamic_columns_value = dynamic_columns_value
217
+
218
+ # Extends where to chain with a has_scope method
219
+ # This scopes the where from above
220
+ def with_scope(opt)
221
+ @dynamic_columns_value[:scope] = opt
222
+
223
+ @scope
224
+ end
225
+ end
226
+
227
+ chain
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -1,189 +1,39 @@
1
+ # ActiveRecord v3 specific changes
2
+
1
3
  module HasDynamicColumns
2
4
  module ActiveRecord
3
5
  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"
6
+ module ClassMethods
7
+ def self.included base
8
+ base.class_eval do
9
+ def preprocess_order_args(order_args)
10
+ order_args.flatten!
11
+ #validate_order_args(order_args)
12
+
13
+ references = order_args.grep(String)
14
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
15
+ references!(references) if references.any?
16
+
17
+ # if a symbol is given we prepend the quoted table name
18
+ order_args.map! do |arg|
19
+ case arg
20
+ when Symbol
21
+ #arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
22
+ table[arg].asc
23
+ when Hash
24
+ arg.map { |field, dir|
25
+ #field = klass.attribute_alias(field) if klass.attribute_alias?(field)
26
+ table[field].send(dir.downcase)
27
+ }
28
+ else
29
+ arg
30
+ end
31
+ end.flatten!
88
32
  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
33
  end
173
34
  end
174
35
  end
175
36
 
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
37
  # lifted from
188
38
  # http://erniemiller.org/2013/10/07/activerecord-where-not-sane-true/
189
39
  class WhereChain
@@ -230,44 +80,6 @@ module HasDynamicColumns
230
80
  end
231
81
  end
232
82
 
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
83
  def order_with_dynamic_columns(opts = :chain)
272
84
  # Chain - by_dynamic_columns
273
85
  if opts == :chain
@@ -1,161 +1,9 @@
1
+ # ActiveRecord v4 specific changes
2
+
1
3
  module HasDynamicColumns
2
4
  module ActiveRecord
3
5
  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
6
+ module ClassMethods
159
7
  end
160
8
 
161
9
  # lifted from
@@ -206,44 +54,6 @@ module HasDynamicColumns
206
54
  end
207
55
  end
208
56
 
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
57
  def order_with_dynamic_columns(opts = :chain)
248
58
  # Chain - by_dynamic_columns
249
59
  if opts == :chain
@@ -0,0 +1,6 @@
1
+ module HasDynamicColumns
2
+ class DynamicColumnModelDatum < ::ActiveRecord::Base
3
+ belongs_to :dynamic_column_datum, :class_name => "HasDynamicColumns::DynamicColumnDatum"
4
+ belongs_to :value, :polymorphic => true
5
+ end
6
+ end
@@ -229,9 +229,9 @@ module HasDynamicColumns
229
229
 
230
230
  @@has_dynamic_columns_configurations.each { |config|
231
231
  if !options[:root].nil?
232
- json[options[:root]][config[:as].to_s] = self.send(config[:as].to_s)
232
+ json[options[:root]][config[:as].to_s] = self.send(config[:as].to_s, true)
233
233
  else
234
- json[config[:as].to_s] = self.send(config[:as].to_s)
234
+ json[config[:as].to_s] = self.send(config[:as].to_s, true)
235
235
  end
236
236
  }
237
237
 
@@ -242,28 +242,55 @@ module HasDynamicColumns
242
242
  def #{configuration[:as]}=data
243
243
  data.each_pair { |key, value|
244
244
  # We dont play well with this key
245
- if !self.storable_#{configuration[:as].to_s.singularize}_key?(key)
246
- raise NoMethodError
247
- end
245
+ raise NoMethodError.new "This key isn't storable" if !self.storable_#{configuration[:as].to_s.singularize}_key?(key)
246
+
248
247
  dynamic_column = self.#{configuration[:as].to_s.singularize}_key_to_dynamic_column(key)
249
248
 
250
- # We already have this key in database
251
- if existing = self.activerecord_dynamic_column_data.select { |i| i.dynamic_column == dynamic_column }.first
252
- existing.value = value
253
- else
254
- self.activerecord_dynamic_column_data.build(:dynamic_column => dynamic_column, :value => value)
255
- end
249
+ # Expecting array data type
250
+ raise ArgumentError.new "Multiple columns must be passed arrays" if dynamic_column.multiple && !value.is_a?(Array)
251
+
252
+ # Treat everything as an array - makes building easier
253
+ value = [value] if !value.is_a?(Array)
254
+
255
+ # Loop each value - sets existing data or builds a new data node
256
+ existing = self.activerecord_dynamic_column_data.select { |i| i.dynamic_column == dynamic_column }
257
+ value.each_with_index { |datum, datum_index|
258
+ if existing[datum_index]
259
+ # Undelete this node if its now needed
260
+ existing[datum_index].reload if existing[datum_index].marked_for_destruction?
261
+ existing[datum_index].value = datum
262
+ # No existing placeholder - build a new one
263
+ else
264
+ self.activerecord_dynamic_column_data.build(:dynamic_column => dynamic_column, :value => datum)
265
+ end
266
+ }
267
+
268
+ # Any record no longer needed should be marked for destruction
269
+ existing.each_with_index { |i,index|
270
+ if index > value.length
271
+ i.mark_for_destruction
272
+ end
273
+ }
256
274
  }
257
275
  end
258
276
 
259
- def #{configuration[:as]}
277
+ def #{configuration[:as]}(as_json = false)
260
278
  h = {}
261
279
  self.field_scope_#{configuration[:as]}.each { |i|
262
- h[i.key] = nil
280
+ h[i.key] = (i.multiple)? [] : nil
263
281
  }
264
282
 
265
283
  self.activerecord_dynamic_column_data.each { |i|
266
- h[i.dynamic_column.key] = i.value unless !i.dynamic_column || !h.has_key?(i.dynamic_column.key)
284
+ if i.dynamic_column && h.has_key?(i.dynamic_column.key)
285
+ v = i.value
286
+ v = v.as_json(:root => nil) if as_json && v.respond_to?(:as_json)
287
+
288
+ if i.dynamic_column.multiple
289
+ h[i.dynamic_column.key] << v
290
+ else
291
+ h[i.dynamic_column.key] = v
292
+ end
293
+ end
267
294
  }
268
295
 
269
296
  h
@@ -274,25 +301,33 @@ module HasDynamicColumns
274
301
  end
275
302
 
276
303
  def field_scope_#{configuration[:as]}
304
+ obj = self.get_#{configuration[:as]}_field_scope
305
+
277
306
  # has_many relationship
278
- if self.get_#{configuration[:as]}_field_scope.respond_to?(:select) && self.get_#{configuration[:as]}_field_scope.respond_to?(:collect)
279
- self.get_#{configuration[:as]}_field_scope.collect { |i|
280
- i.send("activerecord_dynamic_columns")
307
+ if obj.respond_to?(:select) && obj.respond_to?(:collect)
308
+ obj.collect { |i|
309
+ i.send("activerecord_dynamic_columns") if i.respond_to?(:activerecord_dynamic_columns)
281
310
  }.flatten.select { |i|
282
311
  i.dynamic_type.to_s.empty? || i.dynamic_type.to_s == self.class.to_s
283
312
  }
284
313
  # belongs_to relationship
285
- else
286
- self.get_#{configuration[:as]}_field_scope.send("activerecord_dynamic_columns").select { |i|
314
+ elsif obj.respond_to?(:activerecord_dynamic_columns)
315
+ obj.send("activerecord_dynamic_columns").select { |i|
287
316
  # Only get things with no dynamic type defined or dynamic types defined as this class
288
317
  i.dynamic_type.to_s.empty? || i.dynamic_type.to_s == self.class.to_s
289
318
  }
319
+ else
320
+ []
290
321
  end
291
322
  end
292
323
 
293
324
  protected
294
325
  def get_#{configuration[:as]}_field_scope
326
+ # Sometimes association doesnt exist
327
+ begin
295
328
  #{configuration[:field_scope]}
329
+ rescue
330
+ end
296
331
  end
297
332
 
298
333
  # Whether this is storable
@@ -1,3 +1,3 @@
1
1
  module HasDynamicColumns
2
- VERSION = "0.3.3"
2
+ VERSION = "0.3.5"
3
3
  end
@@ -22,6 +22,7 @@ require "has_dynamic_columns/dynamic_column_string_datum"
22
22
  require "has_dynamic_columns/dynamic_column_text_datum"
23
23
  require "has_dynamic_columns/dynamic_column_time_datum"
24
24
  require "has_dynamic_columns/dynamic_column_timestamp_datum"
25
+ require "has_dynamic_columns/dynamic_column_model_datum"
25
26
 
26
27
  module HasDynamicColumns
27
28
  end
@@ -12,6 +12,9 @@ FactoryGirl.define do
12
12
  account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "trusted", :data_type => "boolean")
13
13
  account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "last_contacted", :data_type => "datetime")
14
14
  account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "total_purchases", :data_type => "integer")
15
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "tags", :data_type => "string", :multiple => true)
16
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "address", :data_type => "model", :class_name => "CustomerAddress", :column_name => "name")
17
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "products", :data_type => "model", :multiple => true)
15
18
  end
16
19
  end
17
20
 
@@ -22,9 +22,13 @@ FactoryGirl.define do
22
22
  DateTime.now.change(hour: evaluator.index)
23
23
  when 'boolean'
24
24
  true
25
+ when 'model'
26
+ nil
25
27
  else
26
28
  "#{evaluator.index } - #{i.data_type}"
27
29
  end
30
+
31
+ hash[i.key.to_s] = [hash[i.key.to_s]]if i.multiple
28
32
  }
29
33
  customer.fields = hash
30
34
 
@@ -44,14 +44,63 @@ describe ::HasDynamicColumns::ActiveRecord::QueryMethods do
44
44
  end
45
45
 
46
46
  context 'Customer' do
47
- it 'should order by first_name' do
47
+ it 'should store, retrieve, and search array data' do
48
48
  table = Customer.arel_table
49
49
 
50
+ customer = Customer.new(:account => account, :name => "1")
51
+ customer.fields = {
52
+ "first_name" => "Butch",
53
+ "last_name" => "Marshall",
54
+ "email" => "butch.marshall@unittest.com",
55
+ "trusted" => false,
56
+ "last_contacted" => DateTime.parse("2012-01-01 01:01:01"),
57
+ "total_purchases" => 10,
58
+ "tags" => [
59
+ "a", "b", "c"
60
+ ]
61
+ }
62
+ customer.save
63
+
64
+ customer = Customer.new(:account => account, :name => "1")
65
+ customer.fields = {
66
+ "first_name" => "Merridyth",
67
+ "last_name" => "Marshall",
68
+ "email" => "merridyth.marshall@unittest.com",
69
+ "trusted" => false,
70
+ "last_contacted" => DateTime.parse("2014-05-01 01:01:01"),
71
+ "total_purchases" => 10,
72
+ "tags" => [
73
+ "c", "d", "e"
74
+ ]
75
+ }
76
+ customer.save
77
+
78
+ result = Customer.where
79
+ .has_dynamic_columns(table[:tags].eq("e"))
80
+ .with_scope(account)
81
+ expect(result.length).to eq(1)
82
+
83
+ result = Customer.where
84
+ .has_dynamic_columns(table[:tags].eq("c"))
85
+ .with_scope(account)
86
+ expect(result.length).to eq(2)
87
+
88
+ result = Customer.where
89
+ .has_dynamic_columns(
90
+ table[:tags].eq("c").and(
91
+ table[:first_name].eq("Butch")
92
+ )
93
+ )
94
+ .with_scope(account)
95
+ expect(result.length).to eq(1)
96
+ end
97
+
98
+ it 'should order by first_name' do
50
99
  result = Customer
51
100
  .order
52
101
  .by_dynamic_columns(first_name: :desc)
53
102
  .with_scope(account)
54
- .collect { |i|
103
+ .collect { |i|
55
104
  json = i.as_json(:root => nil)
56
105
  [json["name"], json["fields"]["first_name"], json["fields"]["last_name"]]
57
106
  }
@@ -96,7 +145,7 @@ describe ::HasDynamicColumns::ActiveRecord::QueryMethods do
96
145
  expect(result).to eq([["2", "Butch", "Marshall"], ["1", "Butch", "Casidy"], ["1", "George", "Marshall"]])
97
146
  end
98
147
 
99
- it 'should order by dynamic and regular columns', :focus => true do
148
+ it 'should order by dynamic and regular columns' do
100
149
  table = Customer.arel_table
101
150
 
102
151
  result = Customer
@@ -253,7 +302,7 @@ describe ::HasDynamicColumns::ActiveRecord::QueryMethods do
253
302
 
254
303
  context 'ActiveRecord' do
255
304
  it 'should not clobber where IN queries' do
256
- sql = Account.where("id IN (?)", [1,2,3]).to_sql
305
+ sql = Account.where("id IN (?)", [1,2,3]).to_sql.gsub(/\s\s/,' ')
257
306
  expect(sql).to eq('SELECT "accounts".* FROM "accounts" WHERE (id IN (1,2,3))')
258
307
  end
259
308
  end
@@ -16,7 +16,7 @@ describe HasDynamicColumns::DynamicColumnIntegerDatum do
16
16
  }
17
17
  customer.save
18
18
  json = customer.as_json(:root => nil)
19
- expect(json["fields"]).to eq({"first_name"=>"Butch", "last_name"=>"Marshall", "email"=>nil, "trusted"=>true, "last_contacted"=>nil, "total_purchases"=>123654})
19
+ expect(json["fields"]).to eq({"first_name"=>"Butch", "last_name"=>"Marshall", "email"=>nil, "trusted"=>true, "last_contacted"=>nil, "total_purchases"=>123654, "tags" => [], "address"=>nil, "products"=>[]})
20
20
  end
21
21
 
22
22
  context 'where.has_dynamic_columns' do
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasDynamicColumns::DynamicColumnModelDatum do
4
+ let (:account) do
5
+ FactoryGirl.create(:account_with_customer_dynamic_columns)
6
+ end
7
+
8
+ context 'Customer' do
9
+ it 'should output json as polymoprhic' do
10
+ customer = Customer.new(:account => account)
11
+ customer.fields = {
12
+ "first_name" => "Butch",
13
+ "last_name" => "Marshall",
14
+ "total_purchases" => 123654,
15
+ "trusted" => true,
16
+ "address" => CustomerAddress.new(:name => "1796 18th St, San Francisco, CA 94107, United States"),
17
+ "products" => [
18
+ Product.new(:name => "P1"),
19
+ Product.new(:name => "P2"),
20
+ Product.new(:name => "P3"),
21
+ Product.new(:name => "P4"),
22
+ Product.new(:name => "P5"),
23
+ ]
24
+ }
25
+ customer.save
26
+ json = customer.as_json(:root => nil)
27
+
28
+ expect(json["fields"]["address"]["name"]).to eq("1796 18th St, San Francisco, CA 94107, United States")
29
+ expect(json["fields"]["products"].length).to eq(5)
30
+ # Each of the products should be json
31
+ json["fields"]["products"].each_with_index { |i, index|
32
+ expect(i["name"]).to eq("P#{index+1}")
33
+ }
34
+ end
35
+
36
+ it 'should be searchable', :focus => true do
37
+ table = Customer.arel_table
38
+
39
+ customer = Customer.new(:account => account)
40
+ customer.fields = {
41
+ "first_name" => "Linus",
42
+ "last_name" => "Rorvalds",
43
+ "total_purchases" => 123654,
44
+ "trusted" => true,
45
+ "address" => CustomerAddress.new(:name => "1796 18th St, San Francisco, CA 94107, United States")
46
+ }
47
+ customer.save
48
+
49
+ customer = Customer.new(:account => account)
50
+ customer.fields = {
51
+ "first_name" => "Bill",
52
+ "last_name" => "Gates",
53
+ "total_purchases" => 123654,
54
+ "trusted" => true,
55
+ "address" => CustomerAddress.new(:name => "15010 NE 36th Street, Redmond, WA 98052, United States")
56
+ }
57
+ customer.save
58
+
59
+ customer = Customer.new(:account => account)
60
+ customer.fields = {
61
+ "first_name" => "Mark",
62
+ "last_name" => "Zuceberg",
63
+ "total_purchases" => 123654,
64
+ "trusted" => true,
65
+ "address" => CustomerAddress.new(:name => "1 Hacker Way, Menlo Park, CA 94025, United States")
66
+ }
67
+ customer.save
68
+
69
+ customer = Customer.new(:account => account)
70
+ customer.fields = {
71
+ "first_name" => "Larry",
72
+ "last_name" => "Page",
73
+ "total_purchases" => 123654,
74
+ "trusted" => true,
75
+ "address" => CustomerAddress.new(:name => "1600 Amphitheatre Pkwy, Mountain View, CA 94043, United States")
76
+ }
77
+ customer.save
78
+
79
+ result = Customer
80
+ .where
81
+ .has_dynamic_columns(table[:address].matches("%United States%"))
82
+ .with_scope(account)
83
+ expect(result.length).to eq(4)
84
+
85
+ result = Customer
86
+ .where
87
+ .has_dynamic_columns(table[:address].matches("% CA %"))
88
+ .with_scope(account)
89
+ expect(result.length).to eq(3)
90
+
91
+ result = Customer
92
+ .where
93
+ .has_dynamic_columns(
94
+ table[:address].matches("% Hacker %").or(
95
+ table[:address].matches("1600 %")
96
+ )
97
+ )
98
+ .with_scope(account)
99
+ expect(result.length).to eq(2)
100
+
101
+ result = Customer
102
+ .where
103
+ .has_dynamic_columns(
104
+ table[:address].matches("% Hacker %").or(
105
+ table[:first_name].eq("Larry")
106
+ )
107
+ )
108
+ .with_scope(account)
109
+ .order
110
+ .by_dynamic_columns(
111
+ last_name: :asc
112
+ )
113
+ .with_scope(account)
114
+ expect(result.length).to eq(2)
115
+ expect(result.first.as_json["fields"]["address"]["name"]).to eq("1600 Amphitheatre Pkwy, Mountain View, CA 94043, United States")
116
+ end
117
+ end
118
+ end
data/spec/spec_helper.rb CHANGED
@@ -23,15 +23,23 @@ end
23
23
  # Add this directory so the ActiveSupport autoloading works
24
24
  ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
25
25
 
26
- # Used to test interactions between DJ and an ORM
27
- ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
26
+ if RUBY_PLATFORM == 'java'
27
+ ActiveRecord::Base.establish_connection :adapter => 'jdbcsqlite3', :database => ':memory:'
28
+ else
29
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
30
+ end
31
+
28
32
  ActiveRecord::Migration.verbose = false
29
33
 
30
34
  require "generators/has_dynamic_columns/templates/migration"
31
35
  require "generators/has_dynamic_columns/templates/migration_0.3.0"
36
+ require "generators/has_dynamic_columns/templates/migration_0.3.4"
37
+ require "generators/has_dynamic_columns/templates/migration_0.3.5"
32
38
  ActiveRecord::Schema.define do
33
39
  AddHasDynamicColumns.up
34
40
  Upgrade030HasDynamicColumns.up
41
+ Upgrade034HasDynamicColumns.up
42
+ Upgrade035HasDynamicColumns.up
35
43
 
36
44
  create_table :accounts, force: true do |t|
37
45
  t.string :name
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_dynamic_columns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Butch Marshall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-01 00:00:00.000000000 Z
11
+ date: 2015-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - ">"
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: '4.0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">"
59
59
  - !ruby/object:Gem::Version
60
- version: '0'
60
+ version: '4.0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: bundler
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +97,7 @@ extra_rdoc_files: []
97
97
  files:
98
98
  - ".gitignore"
99
99
  - ".rspec"
100
+ - ".travis.yml"
100
101
  - Gemfile
101
102
  - LICENSE.txt
102
103
  - README.md
@@ -109,7 +110,11 @@ files:
109
110
  - lib/generators/has_dynamic_columns/next_migration_version.rb
110
111
  - lib/generators/has_dynamic_columns/templates/migration.rb
111
112
  - lib/generators/has_dynamic_columns/templates/migration_0.3.0.rb
113
+ - lib/generators/has_dynamic_columns/templates/migration_0.3.4.rb
114
+ - lib/generators/has_dynamic_columns/templates/migration_0.3.5.rb
112
115
  - lib/generators/has_dynamic_columns/upgrade_0_3_0_active_record_generator.rb
116
+ - lib/generators/has_dynamic_columns/upgrade_0_3_4_active_record_generator.rb
117
+ - lib/generators/has_dynamic_columns/upgrade_0_3_5_active_record_generator.rb
113
118
  - lib/has_dynamic_columns.rb
114
119
  - lib/has_dynamic_columns/active_record.rb
115
120
  - lib/has_dynamic_columns/active_record/query_methods.rb
@@ -128,6 +133,7 @@ files:
128
133
  - lib/has_dynamic_columns/dynamic_column_enum_datum.rb
129
134
  - lib/has_dynamic_columns/dynamic_column_float_datum.rb
130
135
  - lib/has_dynamic_columns/dynamic_column_integer_datum.rb
136
+ - lib/has_dynamic_columns/dynamic_column_model_datum.rb
131
137
  - lib/has_dynamic_columns/dynamic_column_option.rb
132
138
  - lib/has_dynamic_columns/dynamic_column_string_datum.rb
133
139
  - lib/has_dynamic_columns/dynamic_column_text_datum.rb
@@ -143,6 +149,7 @@ files:
143
149
  - spec/factories/customer.rb
144
150
  - spec/has_dynamic_columns/active_record/query_methods_spec.rb
145
151
  - spec/has_dynamic_columns/dynamic_columns_integer_datum_spec.rb
152
+ - spec/has_dynamic_columns/dynamic_columns_model_datum_spec.rb
146
153
  - spec/has_dynamic_columns/dynamic_columns_string_datum_spec.rb
147
154
  - spec/has_dynamic_columns_spec.rb
148
155
  - spec/spec_helper.rb
@@ -175,6 +182,7 @@ test_files:
175
182
  - spec/factories/customer.rb
176
183
  - spec/has_dynamic_columns/active_record/query_methods_spec.rb
177
184
  - spec/has_dynamic_columns/dynamic_columns_integer_datum_spec.rb
185
+ - spec/has_dynamic_columns/dynamic_columns_model_datum_spec.rb
178
186
  - spec/has_dynamic_columns/dynamic_columns_string_datum_spec.rb
179
187
  - spec/has_dynamic_columns_spec.rb
180
188
  - spec/spec_helper.rb