has_dynamic_columns 0.2.0 → 0.2.1

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: 8e226dfbdf7a08adfe5c9fe89fd93704b94ce06c
4
- data.tar.gz: f0a3a6de371ded59b17db4690d54093d2ab98d16
3
+ metadata.gz: 448332c912f1846a9b67d402a0be6560fe2562c5
4
+ data.tar.gz: 256541a493f2e38a31683c7076239b42a6da5f6b
5
5
  SHA512:
6
- metadata.gz: 8dd05a3bd91dc8419c67506b463cb0c6741e1756901d9b4979202e4da4ea4c7a01b809de42d33a215f888ea490b458f5321ba2cad17291306d5e092db4bf0399
7
- data.tar.gz: a543799ebbf31103a117652fe127a002e8fa6bbc00870e6c65c5eb54035f7c73af1eb2c8d81eed5eae3bddd3139cddb92c41da28fb749f39ee983511028da179
6
+ metadata.gz: 95a6df76f9a6000ebffeac04915cc8c8f8a422ded713a25f4fda3a02ea52d0c9273fbb37398945d16a0ff26754ffd716e6cf4dd4216bb5571b3f540392b74373
7
+ data.tar.gz: f27225cb04d612c509225626c44625b25f0cc1b1822a4d364e609a6a25e616e538459e3bc368420f3078fa1c78eef43b5d9971adac6ddddcb0d4369d2fe22441
data/README.md CHANGED
@@ -139,30 +139,49 @@ puts customer.as_json
139
139
  account = Account.find(1)
140
140
 
141
141
  # 1 result
142
- Customer.where.has_dynamic_scope({ :first_name => "Butch" }).with_scope(account)
142
+ Customer
143
+ .where
144
+ .has_dynamic_columns({ :first_name => "Butch" })
145
+ .with_scope(account)
143
146
 
144
147
  # 1 result
145
- Customer.where.has_dynamic_scope({ :first_name => "Butch", :company => "Aperture Science" }).with_scope(account)
148
+ Customer
149
+ .where
150
+ .has_dynamic_columns({ :first_name => "Butch", :company => "Aperture Science" })
151
+ .with_scope(account)
146
152
 
147
153
  # 0 results
148
- Customer.where.has_dynamic_scope({ :first_name => "Butch", :company => "Blaaaaa" }).with_scope(account)
154
+ Customer
155
+ .where
156
+ .has_dynamic_columns({ :first_name => "Butch", :company => "Blaaaaa" })
157
+ .with_scope(account)
149
158
 
150
159
  # 2 results
151
- Customer.where.has_dynamic_scope({ :company => "Aperture Science" }).with_scope(account)
160
+ Customer
161
+ .where.has_dynamic_columns({ :company => "Aperture Science" })
162
+ .with_scope(account)
152
163
 
153
164
  # ------------------------------------------------
154
165
  # Find customers under the second account
155
166
  # ------------------------------------------------
156
167
  account = Account.find(2)
157
168
  # 1 result
158
- Customer.where.has_dynamic_scope({ :first_name => "Butch" }).with_scope(account)
169
+ Customer
170
+ .where
171
+ .has_dynamic_columns({ :first_name => "Butch" })
172
+ .with_scope(account)
159
173
 
160
174
  # 1 result
161
- Customer.where.has_dynamic_scope({ :first_name => "Butch", :country => "Canada" }).with_scope(account)
175
+ Customer
176
+ .where
177
+ .has_dynamic_columns({ :first_name => "Butch", :country => "Canada" })
178
+ .with_scope(account)
162
179
 
163
180
  # 0 results
164
- Customer.where.has_dynamic_scope({ :first_name => "Butch", :country => "Japan" }).with_scope(account)
165
- ```
181
+ Customer
182
+ .where
183
+ .has_dynamic_columns({ :first_name => "Butch", :country => "Japan" })
184
+ .with_scope(account)
166
185
 
167
186
  # ------------------------------------------------
168
187
  # without_scope
@@ -170,18 +189,42 @@ Customer.where.has_dynamic_scope({ :first_name => "Butch", :country => "Japan" }
170
189
 
171
190
  # 6 results
172
191
  # finds everyone named butch, no matter what account they're apart of
173
- Customer.where.has_dynamic_scope({ :first_name => "Butch" }).without_scope
192
+ Customer
193
+ .where
194
+ .has_dynamic_columns({ :first_name => "Butch" })
195
+ .without_scope
174
196
 
175
197
  # ------------------------------------------------
176
198
  # with Arel
177
- # WARNING: compound conditionals such as Customer.arel_table[:first_Name].matches("B%").and(Customer.arel_table[:first_Name].eq("Canada")) are NOT currently supported
178
199
  # ------------------------------------------------
179
200
 
180
201
  # 6 matches
181
- Customer.where.has_dynamic_scope(Customer.arel_table[:first_Name].matches("B%")).without_scope
202
+ Customer
203
+ .where.has_dynamic_columns(Customer.arel_table[:first_Name].matches("B%"))
204
+ .without_scope
182
205
 
183
206
  # 1 match
184
- Customer.where.has_dynamic_scope(Customer.arel_table[:first_Name].eq("Canada")).with_scope(Account.find(1))
207
+ Customer
208
+ .where
209
+ .has_dynamic_columns(Customer.arel_table[:country].eq("Canada"))
210
+ .with_scope(Account.find(1))
211
+
212
+ # ------------------------------------------------
213
+ # with nested or/and Arel
214
+ # ------------------------------------------------
215
+
216
+ # Anyone with country: Canada or first_name: John
217
+ # 2 match
218
+ Customer
219
+ .where
220
+ .has_dynamic_columns(
221
+ Customer.arel_table[:country].eq("Canada").or(
222
+ Customer.arel_table[:first_name].eq("John")
223
+ )
224
+ )
225
+ .with_scope(Account.find(1))
226
+
227
+ ```
185
228
 
186
229
  ## **has_many** relationship
187
230
 
@@ -2,46 +2,59 @@ module HasDynamicColumns
2
2
  module ActiveRecord
3
3
  module QueryMethods
4
4
  def self.included(base)
5
-
6
5
  base.class_eval do
7
6
  alias_method_chain :where, :dynamic_columns
8
7
  alias_method_chain :build_arel, :dynamic_columns
9
- end
10
8
 
11
- base.instance_eval do
12
- def has_dynamic_column_values=a
13
- end
14
- def has_dynamic_column_values
9
+ # Recurses through arel nodes until it finds one it can work with
10
+ def dynamic_column_process_nodes(rel, scope, index)
11
+ case rel
12
+ when Arel::Nodes::Grouping
13
+ dynamic_column_process_nodes(rel.expr, scope, index+1)
14
+ when Arel::Nodes::Or
15
+ dynamic_column_process_nodes(rel.left, scope, index+1)
16
+ dynamic_column_process_nodes(rel.right, scope, index+10000) # Hack - queries with over 10,000 dynamic where conditions may break
17
+ when Arel::Nodes::And
18
+ dynamic_column_process_nodes(rel.left, scope, index+1)
19
+ dynamic_column_process_nodes(rel.right, scope, index+10000) # Hack - queries with over 10,000 dynamic where conditions may break
20
+ # We can work with this
21
+ else
22
+ dynamic_column_build_arel_joins_and_modify_wheres(rel, scope, index+1)
23
+ end
15
24
  end
16
- end
17
- end
18
25
 
19
- # When arel starts building - filter
20
- def build_arel_with_dynamic_columns
21
- #arel = Arel::SelectManager.new(table.engine, table)
26
+ # Builds the joins required for this dynamic column
27
+ # Modifies the where to use the dynamic_column_data table alias
28
+ #
29
+ # rel - an arel node
30
+ # scope - scope to run the conditions in
31
+ # index - unique table identifier
32
+ def dynamic_column_build_arel_joins_and_modify_wheres(rel, scope, index)
33
+ col_name = rel.left.name
34
+ value = rel.right
22
35
 
23
- # Calculate any dynamic scope that was passed
24
- self.has_dynamic_columns_values.each { |dynamic_scope|
25
- field_scope = dynamic_scope[:scope]
26
- field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
27
- field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
36
+ field_scope = scope
37
+ field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
38
+ field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
28
39
 
29
- # TODO - make this work on compound arel queries like: table[:last_name].eq("Paterson").or(table[:first_name].eq("John"))
30
- #collapsed = collapse_wheres(arel, dynamic_scope[:where])
40
+ column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{index}_#{col_name}")
41
+ column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{index}_#{col_name}")
31
42
 
32
- dynamic_scope[:where].each_with_index { |rel, index|
33
- case rel
34
- when String
35
- next
43
+ dynamic_type = rel.left.relation.engine.to_s
44
+ rel.left.relation = column_datum_table # modify the where to use the aliased table
45
+ rel.left.name = :value # value is the data storage column searchable on dynamic_column_data table
46
+
47
+ rel.right = case rel.right
48
+ # Map true -> 1
49
+ when ::TrueClass
50
+ 1
51
+ # Map false -> 0
52
+ when ::FalseClass
53
+ 0
36
54
  else
37
- dynamic_type = rel.left.relation.engine.to_s
38
- col_name = rel.left.name
39
- value = rel.right
55
+ rel.right
40
56
  end
41
57
 
42
- column_table = HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{index}_#{col_name}")
43
- column_datum_table = HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{index}_#{col_name}")
44
-
45
58
  # Join on the column with the key
46
59
  on_query = column_table[:key].eq(col_name)
47
60
  on_query = on_query.and(
@@ -60,13 +73,6 @@ module HasDynamicColumns
60
73
  column_table_join = table.create_join(column_table, column_table_join_on)
61
74
  self.joins_values += [column_table_join]
62
75
 
63
- arel_node = case rel
64
- when Arel::Nodes::Equality
65
- column_datum_table[:value].eq(value)
66
- else
67
- column_datum_table[:value].matches(value)
68
- end
69
-
70
76
  # Join on all the data with the provided key
71
77
  column_table_datum_join_on = column_datum_table
72
78
  .create_on(
@@ -74,15 +80,37 @@ module HasDynamicColumns
74
80
  column_datum_table[:owner_type].eq(dynamic_type)
75
81
  ).and(
76
82
  column_datum_table[:dynamic_column_id].eq(column_table[:id])
77
- ).and(
78
- arel_node
79
83
  )
80
84
  )
81
85
 
82
- column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on)
86
+ column_table_datum_join = table.create_join(column_datum_table, column_table_datum_join_on, Arel::Nodes::OuterJoin)
83
87
  self.joins_values += [column_table_datum_join]
88
+
89
+ end
90
+ end
91
+ end
92
+
93
+ # When arel starts building - filter
94
+ def build_arel_with_dynamic_columns
95
+ # Calculate any dynamic scope that was passed
96
+ self.has_dynamic_columns_values.each_with_index { |dynamic_scope, index_outer|
97
+ dynamic_scope[:where].each_with_index { |rel, index_inner|
98
+ # Process each where
99
+ dynamic_column_process_nodes(rel, dynamic_scope[:scope], (index_outer*1000)+(index_inner*10000))
100
+
101
+ # It's now safe to use the original where query
102
+ # All the conditions in rel have been modified to be a where of the aliases dynamic_where_data table
103
+
104
+ # Warning
105
+ # Must cast rel to a string - I've encountered ***strange*** situations where this will change the 'col_name' to value in the where clause
106
+ # specifically, the test 'case should restrict if scope specified' will fail
107
+ self.where_values += [rel.to_sql]
84
108
  }
85
109
  }
110
+ # At least one dynamic where run - we need to group or we're going to get duplicates
111
+ if self.has_dynamic_columns_values.length > 0
112
+ self.group_values += [Arel::Nodes::Group.new(table[:id])]
113
+ end
86
114
 
87
115
  build_arel_without_dynamic_columns
88
116
  end
@@ -134,98 +162,6 @@ module HasDynamicColumns
134
162
  where_without_dynamic_columns(opts, rest)
135
163
  end
136
164
  end
137
- =begin
138
- def where_with_dynamic_columns(opts = :chain, *rest)
139
- puts "--------------------------------------"
140
- puts "Calling where_with_dynamic_columns"
141
- puts opts.inspect
142
- puts rest.inspect
143
-
144
- if opts == :chain
145
- scope = spawn
146
- scope.extend(WhereChainCompatibility)
147
- chain = ::ActiveRecord::QueryMethods::WhereChain.new(scope)
148
- chain.instance_eval do
149
- # Provide search on dynamic columns
150
- def dynamic(opts, *rest)
151
- options = rest.extract_options!
152
- field_scope = opts.respond_to?(:class) && opts.class.respond_to?(:ancestors) && (opts.class.ancestors.include?(::ActiveRecord::Base)) ? opts : nil
153
-
154
- # Field scope passed to the where clause
155
- if !field_scope.nil?
156
- opts = options
157
- rest = []
158
- end
159
-
160
- field_scope_id = (!field_scope.nil?) ? field_scope.id : nil
161
- field_scope_type = (!field_scope.nil?) ? field_scope.class.name.constantize.to_s : nil
162
-
163
- # Join dynamic_column table
164
- # This filter by scope if field_scope is passed
165
- @scope.joins_values += @scope.send(:build_where, opts, rest).map do |rel|
166
- dynamic_type = rel.left.relation.engine.to_s
167
-
168
- col_name = rel.left.name
169
- value = rel.right
170
-
171
- table = dynamic_type.constantize.arel_table
172
- column_table = ::HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{col_name}")
173
-
174
- on_query = column_table[:key].eq(col_name)
175
- if !field_scope_type.nil?
176
- on_query = on_query.and(
177
- column_table[:field_scope_type].eq(field_scope_type)
178
- )
179
- end
180
- if !field_scope_id.nil?
181
- on_query = on_query.and(
182
- column_table[:field_scope_id].eq(field_scope_id)
183
- )
184
- end
185
-
186
- column_table_join_on = column_table.create_on(on_query)
187
- table.create_join(column_table, column_table_join_on)
188
- end unless field_scope.nil?
189
-
190
- # Join dynamic_column_data table
191
- # This filters on data irrigardless of scope
192
- join_value = @scope.send(:build_where, opts, rest).map do |rel|
193
- dynamic_type = rel.left.relation.engine.to_s
194
-
195
- col_name = rel.left.name
196
- value = rel.right
197
-
198
- table = dynamic_type.constantize.arel_table
199
- column_table = ::HasDynamicColumns::DynamicColumn.arel_table.alias("dynamic_where_#{col_name}")
200
- column_datum_table = ::HasDynamicColumns::DynamicColumnDatum.arel_table.alias("dynamic_where_data_#{col_name}")
201
-
202
- # Join on all the data with the provided key
203
- column_table_datum_join_on = column_datum_table
204
- .create_on(
205
- column_datum_table[:owner_id].eq(table[:id]).and(
206
- column_datum_table[:owner_type].eq(dynamic_type)
207
- ).and(
208
- column_datum_table[:dynamic_column_id].eq(column_table[:id])
209
- ).and(
210
- column_datum_table[:value].matches("%"+value+"%")
211
- )
212
- )
213
-
214
- table.create_join(column_datum_table, column_table_datum_join_on)
215
- end
216
- @scope.joins_values += join_value
217
-
218
- @scope
219
- end
220
- end
221
-
222
- chain
223
- else
224
- # Default where
225
- where_without_dynamic_columns(opts, rest)
226
- end
227
- end
228
- =end
229
165
  end
230
166
  end
231
167
  end
@@ -140,6 +140,7 @@ module HasDynamicColumns
140
140
  query
141
141
  end
142
142
 
143
+ # Depricated
143
144
  # Find by dynamic columns
144
145
  def self.dynamic_where(*args)
145
146
  field_scope = args[0].is_a?(Hash) ? nil : args[0]
@@ -1,3 +1,3 @@
1
1
  module HasDynamicColumns
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -38,8 +38,22 @@ describe HasDynamicColumns do
38
38
  account
39
39
  end
40
40
 
41
+ let (:account2) do
42
+ account = Account.new(:name => "Account #2")
41
43
 
42
- describe HasDynamicColumns::ActiveRecord, :focus => true do
44
+ # Setup dynamic fields for Customer under this account
45
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "first_name", :data_type => "string")
46
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "last_name", :data_type => "string")
47
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "country", :data_type => "string")
48
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Customer", :key => "company", :data_type => "string")
49
+
50
+ # Product fields
51
+ account.activerecord_dynamic_columns.build(:dynamic_type => "Product", :key => "rarity", :data_type => "string")
52
+
53
+ account
54
+ end
55
+
56
+ describe HasDynamicColumns::ActiveRecord, :focus => true do
43
57
  it 'should find everyone in the current account scope' do
44
58
  customer = Customer.create(:account => account)
45
59
  customer.fields = {
@@ -71,7 +85,7 @@ describe HasDynamicColumns do
71
85
  customer = Customer.create(:account => account)
72
86
  customer.fields = {
73
87
  "first_name" => "Carl",
74
- "last_name" => "Marx",
88
+ "last_name" => "Paterson",
75
89
  "email" => "carl@communist.com",
76
90
  "trusted" => false,
77
91
  }
@@ -81,17 +95,17 @@ describe HasDynamicColumns do
81
95
 
82
96
  # 1 communist
83
97
  result = Customer.where.has_dynamic_columns(table[:email].matches("%gmail.com")).with_scope(account)
84
- expect(result.length).to eq(3)
98
+ expect(result.all.length).to eq(3)
85
99
 
86
100
  # 2 patersons
87
101
  result = Customer.where.has_dynamic_columns(table[:last_name].eq("Paterson")).with_scope(account)
88
- expect(result.length).to eq(2)
89
-
102
+ expect(result.all.length).to eq(3)
103
+
90
104
  # 1 john paterson
91
105
  result = Customer
92
106
  .where.has_dynamic_columns(table[:first_name].eq("John")).with_scope(account)
93
107
  .where.has_dynamic_columns(table[:last_name].eq("Paterson")).with_scope(account)
94
- expect(result.length).to eq(1)
108
+ expect(result.all.length).to eq(1)
95
109
  end
96
110
 
97
111
  it 'should find the single person in this scope' do
@@ -105,12 +119,88 @@ describe HasDynamicColumns do
105
119
  customer.save
106
120
 
107
121
  result = Customer.where.has_dynamic_columns(Customer.arel_table[:email].matches("%gmail.com")).with_scope(account)
108
- expect(result.length).to eq(1)
122
+ expect(result.all.length).to eq(1)
109
123
  end
110
124
 
111
125
  it 'should find all 4 gmail users when no scope passed' do
112
126
  result = Customer.where.has_dynamic_columns(Customer.arel_table[:email].matches("%gmail.com")).without_scope
113
- expect(result.length).to eq(4)
127
+ expect(result.all.length).to eq(4)
128
+ end
129
+
130
+ it 'should find anyone with first names Steve or John in account 1\'s scope' do
131
+ customer = Customer.create(:account => account)
132
+ customer.fields = {
133
+ "first_name" => "Steve",
134
+ "last_name" => "Jobs",
135
+ "email" => "steve.jobs@apple.com",
136
+ "trusted" => false,
137
+ }
138
+ customer.save
139
+
140
+ result = Customer.where.has_dynamic_columns(Customer.arel_table[:first_name].eq("Steve").or(Customer.arel_table[:first_name].eq("John"))).with_scope(Account.find(1))
141
+ expect(result.all.length).to eq(2)
142
+ end
143
+
144
+ it 'should find anyone with first names Steve or John in any scope' do
145
+ result = Customer.where.has_dynamic_columns(Customer.arel_table[:first_name].eq("Steve").or(Customer.arel_table[:first_name].eq("John"))).without_scope
146
+ expect(result.all.length).to eq(3)
147
+ end
148
+
149
+ it 'should find anyone with first names Steve or John and is trusted in any scope' do
150
+ result = Customer
151
+ .where.has_dynamic_columns(Customer.arel_table[:trusted].eq(true)).without_scope
152
+ .where.has_dynamic_columns(Customer.arel_table[:first_name].eq("Steve").or(Customer.arel_table[:first_name].eq("John"))).without_scope
153
+ expect(result.all.length).to eq(2)
154
+ end
155
+
156
+ it 'should find anyone with first names Steve and is not trusted in any scope' do
157
+ result = Customer
158
+ .where.has_dynamic_columns(Customer.arel_table[:trusted].eq(false)).without_scope
159
+ .where.has_dynamic_columns(Customer.arel_table[:first_name].eq("Steve")).without_scope
160
+ expect(result.all.length).to eq(1)
161
+ end
162
+
163
+ it 'should find all the Steves who are trusted in account 3\'s scope' do
164
+ result = Customer
165
+ .where.has_dynamic_columns(Customer.arel_table[:trusted].eq(true)).with_scope(Account.find(3))
166
+ .where.has_dynamic_columns(Customer.arel_table[:first_name].eq("Steve")).without_scope
167
+ expect(result.all.length).to eq(0)
168
+ end
169
+
170
+ it 'should find all the Steves who are trusted in account 1\'s scope' do
171
+ result = Customer
172
+ .where.has_dynamic_columns(Customer.arel_table[:trusted].eq(true)).with_scope(Account.find(1))
173
+ .where.has_dynamic_columns(Customer.arel_table[:first_name].eq("Steve")).without_scope
174
+ expect(result.all.length).to eq(1)
175
+ end
176
+
177
+ it 'should find across column types if no scope specified' do
178
+ customer = Customer.create(:account => account2)
179
+ customer.fields = {
180
+ "first_name" => "Steve",
181
+ "last_name" => "Jobs",
182
+ "company" => "Apple Computers",
183
+ "country" => "USA",
184
+ }
185
+ customer.save
186
+
187
+ result = Customer
188
+ .where.has_dynamic_columns(
189
+ Customer.arel_table[:first_name].eq("John").or(
190
+ Customer.arel_table[:company].eq("Apple Computers")
191
+ )
192
+ ).without_scope
193
+ expect(result.all.length).to eq(2)
194
+ end
195
+
196
+ it 'should restrict if scope specified' do
197
+ result = Customer
198
+ .where.has_dynamic_columns(
199
+ Customer.arel_table[:first_name].eq("John").or(
200
+ Customer.arel_table[:company].eq("Apple Computers")
201
+ )
202
+ ).with_scope(Account.find(4))
203
+ expect(result.all.length).to eq(1)
114
204
  end
115
205
  end
116
206
 
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.2.0
4
+ version: 0.2.1
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-08-11 00:00:00.000000000 Z
11
+ date: 2015-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord