arel-helpers 2.2.0 → 2.12.0

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
- 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)