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 +5 -5
- data/Gemfile +1 -12
- data/README.md +45 -2
- data/Rakefile +2 -2
- data/arel-helpers.gemspec +16 -14
- data/lib/arel-helpers.rb +2 -1
- data/lib/arel-helpers/aliases.rb +21 -0
- data/lib/arel-helpers/join_association.rb +190 -10
- data/lib/arel-helpers/query_builder.rb +16 -1
- data/lib/arel-helpers/version.rb +1 -1
- data/spec/aliases_spec.rb +40 -0
- data/spec/arel_table_spec.rb +11 -13
- data/spec/env/models.rb +7 -6
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +34 -0
- data/spec/internal/log/test.log +2979 -0
- data/spec/join_association_spec.rb +115 -41
- data/spec/query_builder_spec.rb +36 -17
- data/spec/spec_helper.rb +10 -20
- metadata +101 -14
- data/History.txt +0 -35
- data/spec/env.rb +0 -44
- data/spec/env/migrations.rb +0 -69
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d792fc6a4945a79243f3e4b9720c9f10ce21f5a044328f56f2b381045183589a
|
4
|
+
data.tar.gz: becca530d9ecfd5a25594ce0363cbde8d792d564358d2394a36d7040e6341384
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d825413e0dc2f3caff9ddcbc93a4eadbb1d8814196bc515fd13b3a759969db970a9fe2df911da86def2125d724d5c651e5a39eba2d9cf873d573c913b6a51f5
|
7
|
+
data.tar.gz: 70bea7795e9bcd75058dad5aaee877f6fb1d00e40f9e62b0db8b908d1a1f0656654fe67170a1024e1776b2a458ea11622fad399cda001ffd127c5f35f8197824
|
data/Gemfile
CHANGED
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(:
|
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,
|
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 :
|
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-
|
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
|
-
|
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 =
|
5
|
+
s.name = 'arel-helpers'
|
6
6
|
s.version = ::ArelHelpers::VERSION
|
7
|
-
s.authors = [
|
8
|
-
s.email = [
|
9
|
-
s.homepage =
|
10
|
-
|
11
|
-
s.description = s.summary =
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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[
|
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 :
|
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
|
22
|
-
|
23
|
-
elsif
|
24
|
-
|
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
|
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)
|