has_dynamic_columns 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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