arel-helpers 2.2.0 → 2.12.0

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
- SHA1:
3
- metadata.gz: 875e70afa83c9d6c1c5b74713162b2eef19c94b1
4
- data.tar.gz: 562a0e972c495904a876f5cfcbf08dbadf71b058
2
+ SHA256:
3
+ metadata.gz: d792fc6a4945a79243f3e4b9720c9f10ce21f5a044328f56f2b381045183589a
4
+ data.tar.gz: becca530d9ecfd5a25594ce0363cbde8d792d564358d2394a36d7040e6341384
5
5
  SHA512:
6
- metadata.gz: 2a364dbb1ccbe4937fa23892678261cdfabc4846111499748f244701f210d7a8996465f80f8d14d7c9add1ab4706f3b4a71d48736caa12ffed20f6b6c5f223f1
7
- data.tar.gz: 746357d316c5eb82e29952f2a5fd54ffebae6e550acb7e8eebdf7fcc9bc6562e37975217afde0f8f4e441495b5789c907d0f2a0e22f398ef06d7913e842de0fe
6
+ metadata.gz: 2d825413e0dc2f3caff9ddcbc93a4eadbb1d8814196bc515fd13b3a759969db970a9fe2df911da86def2125d724d5c651e5a39eba2d9cf873d573c913b6a51f5
7
+ data.tar.gz: 70bea7795e9bcd75058dad5aaee877f6fb1d00e40f9e62b0db8b908d1a1f0656654fe67170a1024e1776b2a458ea11622fad399cda001ffd127c5f35f8197824
data/Gemfile CHANGED
@@ -1,14 +1,3 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- group :development, :test do
6
- gem 'pry-nav'
7
- gem 'rake'
8
- end
9
-
10
- group :test do
11
- gem 'rspec', '~> 2.11.0'
12
- gem 'rr', '~> 1.0.4'
13
- gem 'sqlite3'
14
- end
data/README.md CHANGED
@@ -96,6 +96,23 @@ Post.joins(
96
96
 
97
97
  Easy peasy.
98
98
 
99
+ Note that pretty much anything you can pass to ActiveRecord's `#join` method you can also pass to `#join_association`'s second argument. For example, you can pass a hash to indicate a set of nested associations:
100
+
101
+ ```ruby
102
+ Post.joins(
103
+ ArelHelpers.join_association(Post, { comments: :author })
104
+ )
105
+ ```
106
+
107
+ This might execute the following query:
108
+
109
+ ```sql
110
+ SELECT "posts".*
111
+ FROM "posts"
112
+ INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
113
+ INNER JOIN "authors" ON "authors"."id" = "comments"."author_id"
114
+ ```
115
+
99
116
  `#join_association` also allows you to customize the join conditions via a block:
100
117
 
101
118
  ```ruby
@@ -138,7 +155,7 @@ class PostQueryBuilder < ArelHelpers::QueryBuilder
138
155
  def with_comments_by(usernames)
139
156
  reflect(
140
157
  query
141
- .joins(:comments => :author)
158
+ .joins(comments: :author)
142
159
  .where(author[:username].in(usernames))
143
160
  )
144
161
  end
@@ -170,9 +187,35 @@ PostQueryBuilder.new
170
187
  .since_yesterday
171
188
  ```
172
189
 
190
+ #### Conditional reflections
191
+
192
+ If you have parts of a query that should only be added under certain conditions you can return `reflect(query)` from your method. E.g:
193
+
194
+ ```ruby
195
+ def with_comments_by(usernames)
196
+ if usernames
197
+ reflect(
198
+ query.where(post[:title].matches("%#{title}%"))
199
+ )
200
+ else
201
+ reflect(query)
202
+ end
203
+ end
204
+ ```
205
+
206
+ This can become repetitive, and as an alternative you can choose to prepend `not_nil` to your method definition:
207
+
208
+ ```ruby
209
+ class PostQueryBuilder < ArelHelpers::QueryBuilder
210
+ not_nil def with_comments_by(usernames)
211
+ reflect(query.where(post[:title].matches("%#{title}%"))) if usernames
212
+ end
213
+ end
214
+ ```
215
+
173
216
  ## Requirements
174
217
 
175
- Requires ActiveRecord >= 3.1.0, <= 4.2.0, tested with Ruby 1.9.3, 2.0.0, and 2.1.0. Depends on SQLite for testing purposes.
218
+ Requires ActiveRecord >= 3.1.0, < 6, tested against Ruby 2.2.4. Depends on SQLite for testing purposes.
176
219
 
177
220
  ## Running Tests
178
221
 
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ require './lib/arel-helpers'
10
10
 
11
11
  Bundler::GemHelper.install_tasks
12
12
 
13
- task :default => :spec
13
+ task default: :spec
14
14
 
15
15
  desc 'Run specs'
16
16
  RSpec::Core::RakeTask.new do |t|
@@ -21,7 +21,7 @@ task :console do
21
21
  $:.push(File.dirname(__FILE__))
22
22
 
23
23
  require 'spec/env'
24
- require 'pry-nav'
24
+ require 'pry-byebug'
25
25
 
26
26
  ArelHelpers::Env.establish_connection
27
27
  ArelHelpers::Env.reset
data/arel-helpers.gemspec CHANGED
@@ -1,24 +1,26 @@
1
- $:.unshift File.join(File.dirname(__FILE__), 'lib')
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
2
2
  require 'arel-helpers/version'
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.name = "arel-helpers"
5
+ s.name = 'arel-helpers'
6
6
  s.version = ::ArelHelpers::VERSION
7
- s.authors = ["Cameron Dutro"]
8
- s.email = ["camertron@gmail.com"]
9
- s.homepage = "http://github.com/camertron"
10
-
11
- s.description = s.summary = "Useful tools to help construct database queries with ActiveRecord and Arel."
7
+ s.authors = ['Cameron Dutro']
8
+ s.email = ['camertron@gmail.com']
9
+ s.homepage = 'https://github.com/camertron/arel-helpers'
10
+ s.license = 'MIT'
11
+ s.description = s.summary = 'Useful tools to help construct database queries with ActiveRecord and Arel.'
12
12
 
13
13
  s.platform = Gem::Platform::RUBY
14
- s.has_rdoc = true
15
14
 
16
- if ENV["AR"]
17
- s.add_dependency 'activerecord', ENV["AR"]
18
- else
19
- s.add_dependency 'activerecord', '>= 3.1.0', '< 5'
20
- end
15
+ s.add_dependency 'activerecord', '>= 3.1.0', '< 7'
16
+
17
+ s.add_development_dependency 'appraisal'
18
+ s.add_development_dependency 'combustion', '~> 1.3'
19
+ s.add_development_dependency 'database_cleaner', '~> 1.8'
20
+ s.add_development_dependency 'rake', '~> 10.0'
21
+ s.add_development_dependency 'rspec', '~> 3'
22
+ s.add_development_dependency 'sqlite3', '~> 1.4.0'
21
23
 
22
24
  s.require_path = 'lib'
23
- s.files = Dir["{lib,spec}/**/*", "Gemfile", "History.txt", "README.md", "Rakefile", "arel-helpers.gemspec"]
25
+ s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'History.txt', 'README.md', 'Rakefile', 'arel-helpers.gemspec']
24
26
  end
data/lib/arel-helpers.rb CHANGED
@@ -17,8 +17,9 @@ rescue
17
17
  end
18
18
 
19
19
  module ArelHelpers
20
- autoload :JoinAssociation, "arel-helpers/join_association"
20
+ autoload :Aliases, "arel-helpers/aliases"
21
21
  autoload :ArelTable, "arel-helpers/arel_table"
22
+ autoload :JoinAssociation, "arel-helpers/join_association"
22
23
  autoload :QueryBuilder, "arel-helpers/query_builder"
23
24
 
24
25
  def self.join_association(*args, &block)
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+
3
+ module ArelHelpers
4
+
5
+ module Aliases
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def aliased_as(*args)
10
+ aliases = args.map { |name| arel_table.alias(name) }
11
+
12
+ if block_given?
13
+ yield *aliases
14
+ else
15
+ aliases
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ end
@@ -17,19 +17,34 @@ module ArelHelpers
17
17
  # For example, for HABTM associations, two join statements are required.
18
18
  # This method encapsulates that functionality and yields an intermediate object for chaining.
19
19
  # It also allows you to use an outer join instead of the default inner via the join_type arg.
20
- def join_association(table, association, join_type = Arel::Nodes::InnerJoin, &block)
21
- if ActiveRecord::VERSION::STRING >= '4.2.0'
22
- join_association_4_2(table, association, join_type, &block)
23
- elsif ActiveRecord::VERSION::STRING >= '4.1.0'
24
- join_association_4_1(table, association, join_type, &block)
20
+ def join_association(table, association, join_type = Arel::Nodes::InnerJoin, options = {}, &block)
21
+ if version >= '6.1.0'
22
+ join_association_6_1_0(table, association, join_type, options, &block)
23
+ elsif version >= '6.0.0'
24
+ join_association_6_0_0(table, association, join_type, options, &block)
25
+ elsif version >= '5.2.1'
26
+ join_association_5_2_1(table, association, join_type, options, &block)
27
+ elsif version >= '5.2.0'
28
+ join_association_5_2(table, association, join_type, options, &block)
29
+ elsif version >= '5.0.0'
30
+ join_association_5_0(table, association, join_type, options, &block)
31
+ elsif version >= '4.2.0'
32
+ join_association_4_2(table, association, join_type, options, &block)
33
+ elsif version >= '4.1.0'
34
+ join_association_4_1(table, association, join_type, options, &block)
25
35
  else
26
- join_association_3_1(table, association, join_type, &block)
36
+ join_association_3_1(table, association, join_type, options, &block)
27
37
  end
28
38
  end
29
39
 
30
40
  private
31
41
 
32
- def join_association_3_1(table, association, join_type)
42
+ def version
43
+ ActiveRecord::VERSION::STRING
44
+ end
45
+
46
+ def join_association_3_1(table, association, join_type, options = {})
47
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
33
48
  associations = association.is_a?(Array) ? association : [association]
34
49
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
35
50
  manager = Arel::SelectManager.new(table)
@@ -40,8 +55,9 @@ module ArelHelpers
40
55
  end
41
56
 
42
57
  manager.join_sources.map do |assoc|
58
+ assoc.left.table_alias = aliases[assoc.left.name].name if aliases.key?(assoc.left.name)
59
+
43
60
  if block_given?
44
- # yield |assoc_name, join_conditions|
45
61
  right = yield assoc.left.name.to_sym, assoc.right
46
62
  assoc.class.new(assoc.left, right)
47
63
  else
@@ -50,7 +66,8 @@ module ArelHelpers
50
66
  end
51
67
  end
52
68
 
53
- def join_association_4_1(table, association, join_type)
69
+ def join_association_4_1(table, association, join_type, options = {})
70
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
54
71
  associations = association.is_a?(Array) ? association : [association]
55
72
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
56
73
 
@@ -61,6 +78,8 @@ module ArelHelpers
61
78
  constraint.right
62
79
  end
63
80
 
81
+ constraint.left.table_alias = aliases[constraint.left.name].name if aliases.key?(constraint.left.name)
82
+
64
83
  join_type.new(constraint.left, right)
65
84
  end
66
85
  end
@@ -70,7 +89,8 @@ module ArelHelpers
70
89
  # join_association isn't able to add to the list of bind variables
71
90
  # dynamically. To get around the problem, this method must return
72
91
  # a string.
73
- def join_association_4_2(table, association, join_type)
92
+ def join_association_4_2(table, association, join_type, options = {})
93
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
74
94
  associations = association.is_a?(Array) ? association : [association]
75
95
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
76
96
 
@@ -88,6 +108,8 @@ module ArelHelpers
88
108
  join.right
89
109
  end
90
110
 
111
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
112
+
91
113
  join_type.new(join.left, right)
92
114
  end
93
115
  end
@@ -99,6 +121,164 @@ module ArelHelpers
99
121
  join_strings.join(' ')
100
122
  end
101
123
 
124
+ def join_association_5_0(table, association, join_type, options = {})
125
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
126
+ associations = association.is_a?(Array) ? association : [association]
127
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
128
+
129
+ constraints = join_dependency.join_constraints([], join_type)
130
+
131
+ binds = constraints.flat_map do |info|
132
+ prepared_binds = info.binds.map(&:value_for_database)
133
+ prepared_binds.map { |value| table.connection.quote(value) }
134
+ end
135
+
136
+ joins = constraints.flat_map do |constraint|
137
+ constraint.joins.map do |join|
138
+ right = if block_given?
139
+ yield join.left.name.to_sym, join.right
140
+ else
141
+ join.right
142
+ end
143
+
144
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
145
+
146
+ join_type.new(join.left, right)
147
+ end
148
+ end
149
+
150
+ join_strings = joins.map do |join|
151
+ to_sql(join, table, binds)
152
+ end
153
+
154
+ join_strings.join(' ')
155
+ end
156
+
157
+ def join_association_5_2(table, association, join_type, options = {})
158
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
159
+ associations = association.is_a?(Array) ? association : [association]
160
+
161
+ alias_tracker = ActiveRecord::Associations::AliasTracker.create(
162
+ table.connection, table.name, {}
163
+ )
164
+
165
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
166
+ table, table.arel_table, associations, alias_tracker
167
+ )
168
+
169
+ constraints = join_dependency.join_constraints([], join_type)
170
+
171
+ constraints.map do |join|
172
+ right = if block_given?
173
+ yield join.left.name.to_sym, join.right
174
+ else
175
+ join.right
176
+ end
177
+
178
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
179
+
180
+ join_type.new(join.left, right)
181
+ end
182
+ end
183
+
184
+ def join_association_5_2_1(table, association, join_type, options = {})
185
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
186
+ associations = association.is_a?(Array) ? association : [association]
187
+
188
+ alias_tracker = ActiveRecord::Associations::AliasTracker.create(
189
+ table.connection, table.name, {}
190
+ )
191
+
192
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
193
+ table, table.arel_table, associations
194
+ )
195
+
196
+ constraints = join_dependency.join_constraints([], join_type, alias_tracker)
197
+
198
+ constraints.map do |join|
199
+ right = if block_given?
200
+ yield join.left.name.to_sym, join.right
201
+ else
202
+ join.right
203
+ end
204
+
205
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
206
+
207
+ join_type.new(join.left, right)
208
+ end
209
+ end
210
+
211
+ def join_association_6_0_0(table, association, join_type, options = {})
212
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
213
+ associations = association.is_a?(Array) ? association : [association]
214
+
215
+ alias_tracker = ActiveRecord::Associations::AliasTracker.create(
216
+ table.connection, table.name, {}
217
+ )
218
+
219
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
220
+ table, table.arel_table, associations, join_type
221
+ )
222
+
223
+ constraints = join_dependency.join_constraints([], alias_tracker)
224
+
225
+ constraints.map do |join|
226
+ right = if block_given?
227
+ yield join.left.name.to_sym, join.right
228
+ else
229
+ join.right
230
+ end
231
+
232
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
233
+
234
+ join_type.new(join.left, right)
235
+ end
236
+ end
237
+
238
+ def join_association_6_1_0(table, association, join_type, options = {})
239
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
240
+ associations = association.is_a?(Array) ? association : [association]
241
+
242
+ alias_tracker = ActiveRecord::Associations::AliasTracker.create(
243
+ table.connection, table.name, {}
244
+ )
245
+
246
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
247
+ table, table.arel_table, associations, join_type
248
+ )
249
+
250
+ constraints = join_dependency.join_constraints([], alias_tracker, [])
251
+
252
+ constraints.map do |join|
253
+ apply_aliases(join, aliases)
254
+
255
+ right = if block_given?
256
+ yield join.left.name.to_sym, join.right
257
+ else
258
+ join.right
259
+ end
260
+
261
+ join_type.new(join.left, right)
262
+ end
263
+ end
264
+
265
+ def apply_aliases(node, aliases)
266
+ case node
267
+ when Arel::Nodes::Join
268
+ node.left = aliases[node.left.name] || node.left
269
+ apply_aliases(node.right, aliases)
270
+ when Arel::Attributes::Attribute
271
+ node.relation = aliases[node.relation.name] || node.relation
272
+ when Arel::Nodes::And
273
+ node.children.each { |child| apply_aliases(child, aliases) }
274
+ when Arel::Nodes::Unary
275
+ apply_aliases(node.value, aliases)
276
+ when Arel::Nodes::Binary
277
+ apply_aliases(node.left, aliases)
278
+ apply_aliases(node.right, aliases)
279
+ end
280
+ end
281
+
102
282
  private
103
283
 
104
284
  def to_sql(node, table, binds)