arel-helpers 2.11.0 → 2.14.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
2
  SHA256:
3
- metadata.gz: b15a810e483ddccf300cec2e2926a44b05be707e557fa7f89d432fa6542ddc72
4
- data.tar.gz: 8acaece31ba468798d127deab33ac06c8569b71d51e0a813902f398a5f3efbcc
3
+ metadata.gz: 667479f8a68b5bb0af49054dea8aad1071976d752ca00f1e6cd4560005b71ba7
4
+ data.tar.gz: 0d7d43d81c584cf98a7a2becb0cc61e9f36af9cc24296146d0e4857855a9ef4b
5
5
  SHA512:
6
- metadata.gz: 265f9cd3e9cbc2d8e3b4c4aa6cb4d2b3cbab4ab8cba267a7088ec56da4cc038271da0ca51d3e2d2d3923cf04d04fc4c8ee79fda0cd571f2b59561677b8dba6d2
7
- data.tar.gz: 60de9c7c46e7d1ed33f8d5ac9466613075e8ab1fd0b9a3335a1f88c6ccc19b64a388297c151578fb23d090c58847151a01c4af5a3365702941fb607c9f0512c5
6
+ metadata.gz: 07545d1df75177211b9e40302b3273df2eac33d29dc3ad42fa8c737b06711f2cdbf6455e1e2b90cb523a7248bfece31e8b16296501c103a8f65c513cf21dde02
7
+ data.tar.gz: 978312ef3268e698c8b155fad5bdbdf084a5175a1a753b8367386d8f116af2fbacb58601288987a3e8cc3a6755eb1efee9412eb459aec72a1990ecb6112e8c1a
data/Gemfile CHANGED
@@ -1,2 +1,3 @@
1
- ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile-rails-6.0.x', __FILE__)
2
- Bundler.load
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
+ ![Unit Tests](https://github.com/camertron/arel-helpers/actions/workflows/unit_tests.yml/badge.svg?branch=master)
1
2
 
2
-
3
- ## arel-helpers [![Build Status](https://secure.travis-ci.org/camertron/arel-helpers.png?branch=master)](http://travis-ci.org/camertron/arel-helpers)
3
+ ## arel-helpers
4
4
 
5
5
  Useful tools to help construct database queries with ActiveRecord and Arel.
6
6
 
@@ -215,7 +215,7 @@ This can become repetitive, and as an alternative you can choose to prepend `not
215
215
 
216
216
  ## Requirements
217
217
 
218
- Requires ActiveRecord >= 3.1.0, < 6, tested against Ruby 2.2.4. Depends on SQLite for testing purposes.
218
+ Requires ActiveRecord >= 3.1.0, < 7. Depends on SQLite for testing purposes.
219
219
 
220
220
  ## Running Tests
221
221
 
data/arel-helpers.gemspec CHANGED
@@ -1,23 +1,27 @@
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 = "https://github.com/camertron/arel-helpers"
7
+ s.authors = ['Cameron Dutro']
8
+ s.email = ['camertron@gmail.com']
9
+ s.homepage = 'https://github.com/camertron/arel-helpers'
10
10
  s.license = 'MIT'
11
- s.description = s.summary = "Useful tools to help construct database queries with ActiveRecord and Arel."
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
14
 
15
- s.add_dependency 'activerecord', '>= 3.1.0', '< 7'
15
+ s.add_dependency 'activerecord', '>= 3.1.0', '< 8'
16
16
 
17
- s.add_development_dependency 'rake', '~> 10.0'
18
- s.add_development_dependency 'rspec', '~> 2.11'
19
- s.add_development_dependency 'rr', '~> 1.0'
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'
21
+ s.add_development_dependency 'rspec', '~> 3'
22
+ s.add_development_dependency 'sqlite3', '~> 1.4.0'
20
23
 
21
24
  s.require_path = 'lib'
22
- 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']
26
+ s.files -= Dir['spec/internal/log', 'spec/internal/log/**/*', 'spec/internal/db', 'spec/internal/db/**/*']
23
27
  end
@@ -18,7 +18,9 @@ module ArelHelpers
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
20
  def join_association(table, association, join_type = Arel::Nodes::InnerJoin, options = {}, &block)
21
- if version >= '6.0.0'
21
+ if version >= '6.1.0'
22
+ join_association_6_1_0(table, association, join_type, options, &block)
23
+ elsif version >= '6.0.0'
22
24
  join_association_6_0_0(table, association, join_type, options, &block)
23
25
  elsif version >= '5.2.1'
24
26
  join_association_5_2_1(table, association, join_type, options, &block)
@@ -42,7 +44,7 @@ module ArelHelpers
42
44
  end
43
45
 
44
46
  def join_association_3_1(table, association, join_type, options = {})
45
- aliases = options.fetch(:aliases, {})
47
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
46
48
  associations = association.is_a?(Array) ? association : [association]
47
49
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
48
50
  manager = Arel::SelectManager.new(table)
@@ -53,12 +55,9 @@ module ArelHelpers
53
55
  end
54
56
 
55
57
  manager.join_sources.map do |assoc|
56
- if found_alias = find_alias(assoc.left.name, aliases)
57
- assoc.left.table_alias = found_alias.name
58
- end
58
+ assoc.left.table_alias = aliases[assoc.left.name].name if aliases.key?(assoc.left.name)
59
59
 
60
60
  if block_given?
61
- # yield |assoc_name, join_conditions|
62
61
  right = yield assoc.left.name.to_sym, assoc.right
63
62
  assoc.class.new(assoc.left, right)
64
63
  else
@@ -68,7 +67,7 @@ module ArelHelpers
68
67
  end
69
68
 
70
69
  def join_association_4_1(table, association, join_type, options = {})
71
- aliases = options.fetch(:aliases, {})
70
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
72
71
  associations = association.is_a?(Array) ? association : [association]
73
72
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
74
73
 
@@ -79,9 +78,7 @@ module ArelHelpers
79
78
  constraint.right
80
79
  end
81
80
 
82
- if found_alias = find_alias(constraint.left.name, aliases)
83
- constraint.left.table_alias = found_alias.name
84
- end
81
+ constraint.left.table_alias = aliases[constraint.left.name].name if aliases.key?(constraint.left.name)
85
82
 
86
83
  join_type.new(constraint.left, right)
87
84
  end
@@ -93,7 +90,7 @@ module ArelHelpers
93
90
  # dynamically. To get around the problem, this method must return
94
91
  # a string.
95
92
  def join_association_4_2(table, association, join_type, options = {})
96
- aliases = options.fetch(:aliases, [])
93
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
97
94
  associations = association.is_a?(Array) ? association : [association]
98
95
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
99
96
 
@@ -111,9 +108,7 @@ module ArelHelpers
111
108
  join.right
112
109
  end
113
110
 
114
- if found_alias = find_alias(join.left.name, aliases)
115
- join.left.table_alias = found_alias.name
116
- end
111
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
117
112
 
118
113
  join_type.new(join.left, right)
119
114
  end
@@ -127,7 +122,7 @@ module ArelHelpers
127
122
  end
128
123
 
129
124
  def join_association_5_0(table, association, join_type, options = {})
130
- aliases = options.fetch(:aliases, [])
125
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
131
126
  associations = association.is_a?(Array) ? association : [association]
132
127
  join_dependency = ActiveRecord::Associations::JoinDependency.new(table, associations, [])
133
128
 
@@ -146,9 +141,7 @@ module ArelHelpers
146
141
  join.right
147
142
  end
148
143
 
149
- if found_alias = find_alias(join.left.name, aliases)
150
- join.left.table_alias = found_alias.name
151
- end
144
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
152
145
 
153
146
  join_type.new(join.left, right)
154
147
  end
@@ -162,7 +155,7 @@ module ArelHelpers
162
155
  end
163
156
 
164
157
  def join_association_5_2(table, association, join_type, options = {})
165
- aliases = options.fetch(:aliases, [])
158
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
166
159
  associations = association.is_a?(Array) ? association : [association]
167
160
 
168
161
  alias_tracker = ActiveRecord::Associations::AliasTracker.create(
@@ -182,16 +175,14 @@ module ArelHelpers
182
175
  join.right
183
176
  end
184
177
 
185
- if found_alias = find_alias(join.left.name, aliases)
186
- join.left.table_alias = found_alias.name
187
- end
178
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
188
179
 
189
180
  join_type.new(join.left, right)
190
181
  end
191
182
  end
192
183
 
193
184
  def join_association_5_2_1(table, association, join_type, options = {})
194
- aliases = options.fetch(:aliases, [])
185
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
195
186
  associations = association.is_a?(Array) ? association : [association]
196
187
 
197
188
  alias_tracker = ActiveRecord::Associations::AliasTracker.create(
@@ -211,16 +202,14 @@ module ArelHelpers
211
202
  join.right
212
203
  end
213
204
 
214
- if found_alias = find_alias(join.left.name, aliases)
215
- join.left.table_alias = found_alias.name
216
- end
205
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
217
206
 
218
207
  join_type.new(join.left, right)
219
208
  end
220
209
  end
221
210
 
222
211
  def join_association_6_0_0(table, association, join_type, options = {})
223
- aliases = options.fetch(:aliases, [])
212
+ aliases = options.fetch(:aliases, []).index_by(&:table_name)
224
213
  associations = association.is_a?(Array) ? association : [association]
225
214
 
226
215
  alias_tracker = ActiveRecord::Associations::AliasTracker.create(
@@ -240,14 +229,56 @@ module ArelHelpers
240
229
  join.right
241
230
  end
242
231
 
243
- if found_alias = find_alias(join.left.name, aliases)
244
- join.left.table_alias = found_alias.name
245
- end
232
+ join.left.table_alias = aliases[join.left.name].name if aliases.key?(join.left.name)
246
233
 
247
234
  join_type.new(join.left, right)
248
235
  end
249
236
  end
250
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
+
251
282
  private
252
283
 
253
284
  def to_sql(node, table, binds)
@@ -255,10 +286,6 @@ module ArelHelpers
255
286
  collect = visitor.accept(node, Arel::Collectors::Bind.new)
256
287
  collect.substitute_binds(binds).join
257
288
  end
258
-
259
- def find_alias(name, aliases)
260
- aliases.find { |a| a.table_name == name }
261
- end
262
289
  end
263
290
  end
264
291
  end
@@ -9,7 +9,7 @@ module ArelHelpers
9
9
  include Enumerable
10
10
 
11
11
  attr_reader :query
12
- def_delegators :@query, :to_a, :to_sql, :each
12
+ def_delegators :@query, :to_a, :to_sql, :each, :empty?, :size
13
13
 
14
14
  TERMINAL_METHODS = [:count, :first, :last]
15
15
  TERMINAL_METHODS << :pluck if ActiveRecord::VERSION::MAJOR >= 4
@@ -18,8 +18,8 @@ module ArelHelpers
18
18
 
19
19
  def self.not_nil(name)
20
20
  mod = Module.new do
21
- define_method(name) do |*args|
22
- if (value = super(*args))
21
+ define_method(name) do |*args, **kwargs|
22
+ if (value = super(*args, **kwargs))
23
23
  value
24
24
  else
25
25
  reflect(query)
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module ArelHelpers
4
- VERSION = '2.11.0'
4
+ VERSION = '2.14.0'
5
5
  end
data/spec/aliases_spec.rb CHANGED
@@ -1,17 +1,15 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ArelHelpers::Aliases do
6
- describe "#aliased_as" do
7
- it "yields an alias when passed a block" do
4
+ describe '#aliased_as' do
5
+ it 'yields an alias when passed a block' do
8
6
  Post.aliased_as('foo') do |foo_alias|
9
7
  expect(foo_alias).to be_a(Arel::Nodes::TableAlias)
10
8
  expect(foo_alias.name).to eq('foo')
11
9
  end
12
10
  end
13
11
 
14
- it "is capable of yielding multiple aliases" do
12
+ it 'is capable of yielding multiple aliases' do
15
13
  Post.aliased_as('foo', 'bar') do |foo_alias, bar_alias|
16
14
  expect(foo_alias).to be_a(Arel::Nodes::TableAlias)
17
15
  expect(foo_alias.name).to eq('foo')
@@ -21,14 +19,14 @@ describe ArelHelpers::Aliases do
21
19
  end
22
20
  end
23
21
 
24
- it "returns an alias when not passed a block" do
22
+ it 'returns an alias when not passed a block' do
25
23
  aliases = Post.aliased_as('foo')
26
24
  expect(aliases.size).to eq(1)
27
25
  expect(aliases[0]).to be_a(Arel::Nodes::TableAlias)
28
26
  expect(aliases[0].name).to eq('foo')
29
27
  end
30
28
 
31
- it "is capable of returning multiple aliases" do
29
+ it 'is capable of returning multiple aliases' do
32
30
  aliases = Post.aliased_as('foo', 'bar')
33
31
  expect(aliases.size).to eq(2)
34
32
 
@@ -1,30 +1,28 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ArelHelpers::ArelTable do
6
- it "should add the [] function to the model and allow attribute access" do
4
+ it 'should add the [] function to the model and allow attribute access' do
7
5
  Post[:id].tap do |post_id|
8
- post_id.should be_a(Arel::Attribute)
9
- post_id.name.should == :id
10
- post_id.relation.name.should == "posts"
6
+ expect(post_id).to be_a Arel::Attribute
7
+ expect(post_id.name.to_s).to eq 'id'
8
+ expect(post_id.relation.name).to eq 'posts'
11
9
  end
12
10
  end
13
11
 
14
- it "should not interfere with associations" do
12
+ it 'should not interfere with associations' do
15
13
  post = Post.create(title: "I'm a little teapot")
16
- post.comments[0].should be_nil
14
+ expect(post.comments[0]).to be_nil
17
15
  end
18
16
 
19
- it "should allow retrieving associated records" do
17
+ it 'should allow retrieving associated records' do
20
18
  post = Post.create(title: "I'm a little teapot")
21
19
  comment = post.comments.create
22
- post.reload.comments[0].id.should == comment.id
20
+ expect(post.reload.comments[0].id).to eq comment.id
23
21
  end
24
22
 
25
- it "does not interfere with ActiveRecord::Relation objects" do
26
- Post.all[0].should be_nil
23
+ it 'does not interfere with ActiveRecord::Relation objects' do
24
+ expect(Post.all[0]).to be_nil
27
25
  p = Post.create(title: 'foo')
28
- Post.all[0].id.should == p.id
26
+ expect(Post.all[0].id).to eq p.id
29
27
  end
30
28
  end
data/spec/env/models.rb CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: UTF-8
2
-
3
1
  class Post < ActiveRecord::Base
4
2
  include ArelHelpers::ArelTable
5
3
  include ArelHelpers::Aliases
@@ -44,12 +42,10 @@ end
44
42
 
45
43
  class Location < ActiveRecord::Base
46
44
  has_many :card_locations
47
- has_many :community_tickets, {
48
- through: :card_locations, source: :card, source_type: 'CommunityTicket'
49
- }
45
+ has_many :community_tickets, through: :card_locations, source: :card, source_type: 'CommunityTicket'
50
46
  end
51
47
 
52
48
  class CommunityTicket < ActiveRecord::Base
53
- has_many :card_locations, as: :card, source_type: 'CommunityTicket'
49
+ has_many :card_locations, as: :card
54
50
  has_many :locations, through: :card_locations
55
51
  end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/combustion_test.sqlite
@@ -1,94 +1,137 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  describe ArelHelpers do
6
- describe "#join_association" do
7
- it "should work for a directly associated model" do
8
- Post.joins(ArelHelpers.join_association(Post, :comments)).to_sql.should ==
9
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"'
4
+ describe '#join_association' do
5
+ it 'should work for a directly associated model' do
6
+ expect(Post.joins(ArelHelpers.join_association(Post, :comments)).to_sql).to(
7
+ eq('SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"')
8
+ )
10
9
  end
11
10
 
12
- it "should work with an outer join" do
13
- Post.joins(ArelHelpers.join_association(Post, :comments, Arel::Nodes::OuterJoin)).to_sql.should ==
14
- 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"'
11
+ it 'should work with an outer join' do
12
+ expect(Post.joins(ArelHelpers.join_association(Post, :comments, Arel::Nodes::OuterJoin)).to_sql).to(
13
+ eq('SELECT "posts".* FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"')
14
+ )
15
15
  end
16
16
 
17
- it "should allow adding additional join conditions" do
18
- Post.joins(ArelHelpers.join_association(Post, :comments) do |assoc_name, join_conditions|
17
+ it 'should allow adding additional join conditions' do
18
+ sql = Post.joins(ArelHelpers.join_association(Post, :comments) do |_assoc_name, join_conditions|
19
19
  join_conditions.and(Comment[:id].eq(10))
20
- end).to_sql.should ==
21
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" AND "comments"."id" = 10'
20
+ end).to_sql
21
+
22
+ expect(sql).to eq <<-SQL.squish
23
+ SELECT "posts".* FROM "posts"
24
+ INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" AND "comments"."id" = 10
25
+ SQL
22
26
  end
23
27
 
24
- it "should work for two models, one directly associated and the other indirectly" do
25
- Post
26
- .joins(ArelHelpers.join_association(Post, :comments))
27
- .joins(ArelHelpers.join_association(Comment, :author))
28
- .to_sql.should ==
29
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" INNER JOIN "authors" ON "authors"."id" = "comments"."author_id"'
28
+ it 'should work for two models, one directly associated and the other indirectly' do
29
+ sql = Post
30
+ .joins(ArelHelpers.join_association(Post, :comments))
31
+ .joins(ArelHelpers.join_association(Comment, :author))
32
+ .to_sql
33
+
34
+ expect(sql).to eq <<-SQL.squish
35
+ SELECT "posts".* FROM "posts"
36
+ INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
37
+ INNER JOIN "authors" ON "authors"."id" = "comments"."author_id"
38
+ SQL
30
39
  end
31
40
 
32
- it "should work for a nested hash of association names" do
33
- Post
34
- .joins(ArelHelpers.join_association(Post, { comments: :author }))
35
- .to_sql.should ==
36
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" INNER JOIN "authors" ON "authors"."id" = "comments"."author_id"'
41
+ it 'should work for a nested hash of association names' do
42
+ sql = Post
43
+ .joins(ArelHelpers.join_association(Post, { comments: :author }))
44
+ .to_sql
45
+
46
+ expect(sql).to eq <<-SQL.squish
47
+ SELECT "posts".* FROM "posts"
48
+ INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
49
+ INNER JOIN "authors" ON "authors"."id" = "comments"."author_id"
50
+ SQL
37
51
  end
38
52
 
39
- it "should work with outer joins when given a nested hash of association names" do
40
- Post
41
- .joins(ArelHelpers.join_association(Post, { comments: :author }, Arel::Nodes::OuterJoin))
42
- .to_sql.should ==
43
- 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" LEFT OUTER JOIN "authors" ON "authors"."id" = "comments"."author_id"'
53
+ it 'should work with outer joins when given a nested hash of association names' do
54
+ sql = Post.joins(ArelHelpers.join_association(Post, { comments: :author }, Arel::Nodes::OuterJoin)).to_sql
55
+
56
+ expect(sql).to eq <<-SQL.squish
57
+ SELECT "posts".* FROM "posts"
58
+ LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
59
+ LEFT OUTER JOIN "authors" ON "authors"."id" = "comments"."author_id"
60
+ SQL
44
61
  end
45
62
 
46
- it "should be able to handle multiple associations" do
47
- Post.joins(ArelHelpers.join_association(Post, [:comments, :favorites])).to_sql.should ==
48
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" INNER JOIN "favorites" ON "favorites"."post_id" = "posts"."id"'
63
+ it 'should be able to handle multiple associations' do
64
+ expect(Post.joins(ArelHelpers.join_association(Post, %i[comments favorites])).to_sql).to(
65
+ eq <<-SQL.squish
66
+ SELECT "posts".* FROM "posts"
67
+ INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
68
+ INNER JOIN "favorites" ON "favorites"."post_id" = "posts"."id"
69
+ SQL
70
+ )
49
71
  end
50
72
 
51
- it "should yield once for each association" do
52
- Post.joins(ArelHelpers.join_association(Post, [:comments, :favorites]) do |assoc_name, join_conditions|
73
+ it 'should yield once for each association' do
74
+ sql = Post.joins(ArelHelpers.join_association(Post, %i[comments favorites]) do |assoc_name, join_conditions|
53
75
  case assoc_name
54
- when :favorites
55
- join_conditions.or(Favorite[:amount].eq("lots"))
56
- when :comments
57
- join_conditions.and(Comment[:text].eq("Awesome post!"))
76
+ when :favorites
77
+ join_conditions.or(Favorite[:amount].eq('lots'))
78
+ when :comments
79
+ join_conditions.and(Comment[:text].eq('Awesome post!'))
58
80
  end
59
- end).to_sql.should ==
60
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" AND "comments"."text" = \'Awesome post!\' INNER JOIN "favorites" (ON "favorites"."post_id" = "posts"."id" OR "favorites"."amount" = \'lots\')'
81
+ end).to_sql
82
+
83
+ expect(sql).to eq <<-SQL.squish
84
+ SELECT "posts".* FROM "posts"
85
+ INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" AND "comments"."text" = 'Awesome post!'
86
+ INNER JOIN "favorites" (ON "favorites"."post_id" = "posts"."id" OR "favorites"."amount" = 'lots')
87
+ SQL
61
88
  end
62
89
 
63
90
  it 'should be able to handle has_and_belongs_to_many associations' do
64
- CollabPost.joins(ArelHelpers.join_association(CollabPost, :authors)).to_sql.should ==
65
- 'SELECT "collab_posts".* FROM "collab_posts" INNER JOIN "authors_collab_posts" ON "authors_collab_posts"."collab_post_id" = "collab_posts"."id" INNER JOIN "authors" ON "authors"."id" = "authors_collab_posts"."author_id"'
91
+ sql = CollabPost.joins(ArelHelpers.join_association(CollabPost, :authors)).to_sql
92
+
93
+ expect(sql).to eq <<-SQL.squish
94
+ SELECT "collab_posts".* FROM "collab_posts"
95
+ INNER JOIN "authors_collab_posts" ON "authors_collab_posts"."collab_post_id" = "collab_posts"."id"
96
+ INNER JOIN "authors" ON "authors"."id" = "authors_collab_posts"."author_id"
97
+ SQL
66
98
  end
67
99
 
68
- it "allows adding a custom alias to the joined table" do
100
+ it 'allows adding a custom alias to the joined table' do
69
101
  Comment.aliased_as(:foo) do |foo|
70
- Post.joins(ArelHelpers.join_association(Post, :comments, Arel::Nodes::InnerJoin, aliases: [foo])).to_sql.should ==
71
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" "foo" ON "foo"."post_id" = "posts"."id"'
102
+ sql = Post.joins(
103
+ ArelHelpers.join_association(Post, :comments, Arel::Nodes::InnerJoin, aliases: [foo])
104
+ ).to_sql
105
+
106
+ expect(sql).to eq 'SELECT "posts".* FROM "posts" INNER JOIN "comments" "foo" ON "foo"."post_id" = "posts"."id"'
72
107
  end
73
108
  end
74
109
 
75
- it "allows aliasing multiple associations" do
110
+ it 'allows aliasing multiple associations' do
76
111
  Comment.aliased_as(:foo) do |foo|
77
112
  Favorite.aliased_as(:bar) do |bar|
78
- Post.joins(ArelHelpers.join_association(Post, [:comments, :favorites], Arel::Nodes::InnerJoin, aliases: [foo, bar])).to_sql.should ==
79
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" "foo" ON "foo"."post_id" = "posts"."id" INNER JOIN "favorites" "bar" ON "bar"."post_id" = "posts"."id"'
113
+ sql = Post.joins(
114
+ ArelHelpers.join_association(Post, %i[comments favorites], Arel::Nodes::InnerJoin, aliases: [foo, bar])
115
+ ).to_sql
116
+
117
+ expect(sql).to eq <<-SQL.squish
118
+ SELECT "posts".* FROM "posts"
119
+ INNER JOIN "comments" "foo" ON "foo"."post_id" = "posts"."id"
120
+ INNER JOIN "favorites" "bar" ON "bar"."post_id" = "posts"."id"
121
+ SQL
80
122
  end
81
123
  end
82
124
  end
83
125
 
84
126
  it 'handles polymorphic through associations' do
85
- relation = Location.joins(
86
- ArelHelpers.join_association(Location, :community_tickets)
87
- )
127
+ location = Location.create!
128
+ ticket = CommunityTicket.create!
129
+ CardLocation.create! card: ticket, location: location
130
+
131
+ relation = Location.joins(ArelHelpers.join_association(Location, :community_tickets))
88
132
 
89
- relation.to_sql.should == 'SELECT "locations".* FROM "locations" ' +
90
- 'INNER JOIN "card_locations" ON "card_locations"."location_id" = "locations"."id" AND "card_locations"."card_type" = \'CommunityTicket\' ' +
91
- 'INNER JOIN "community_tickets" ON "community_tickets"."id" = "card_locations"."card_id"'
133
+ expect(relation.count).to eq 1
134
+ expect(relation.to_a).to include location
92
135
  end
93
136
  end
94
137
  end
@@ -98,8 +141,9 @@ describe ArelHelpers::JoinAssociation do
98
141
  include ArelHelpers::JoinAssociation
99
142
  end
100
143
 
101
- it "should provide the join_association method and use the parent class as the model to join on" do
102
- AssocPost.joins(AssocPost.join_association(:comments)).to_sql.should ==
103
- 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"'
144
+ it 'should provide the join_association method and use the parent class as the model to join on' do
145
+ expect(AssocPost.joins(AssocPost.join_association(:comments)).to_sql).to eq <<-SQL.squish
146
+ SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
147
+ SQL
104
148
  end
105
149
  end
@@ -1,5 +1,3 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  class TestQueryBuilder < ArelHelpers::QueryBuilder
@@ -8,6 +6,9 @@ class TestQueryBuilder < ArelHelpers::QueryBuilder
8
6
 
9
7
  def initialize(query = nil)
10
8
  super(query || Post.unscoped)
9
+
10
+ # only necessary for the test environment to prevent pollution between tests
11
+ @query.reload
11
12
  end
12
13
 
13
14
  def noop
@@ -15,60 +16,69 @@ class TestQueryBuilder < ArelHelpers::QueryBuilder
15
16
  end
16
17
 
17
18
  not_nil def optional(skip:)
18
- reflect(query.where(title: "BarBar")) unless skip
19
+ reflect(query.where(title: 'BarBar')) unless skip
19
20
  end
20
21
  end
21
22
 
22
23
  describe ArelHelpers::QueryBuilder do
23
24
  let(:builder) { TestQueryBuilder.new }
24
25
 
25
- it "returns (i.e. reflects) new instances of QueryBuilder" do
26
+ it 'returns (i.e. reflects) new instances of QueryBuilder' do
26
27
  builder.tap do |original_builder|
27
28
  original_builder.params = true
28
29
  new_builder = original_builder.noop
29
- new_builder.object_id.should_not == original_builder.object_id
30
- new_builder.params?.should == true
30
+ expect(new_builder.object_id).not_to eq original_builder.object_id
31
+ expect(new_builder.params?).to be true
31
32
  end
32
33
  end
33
34
 
34
- it "forwards #to_a" do
35
- Post.create(title: "Foobar")
35
+ it 'forwards #to_a' do
36
+ Post.create(title: 'Foobar')
36
37
  builder.to_a.tap do |posts|
37
- posts.size.should == 1
38
- posts.first.title.should == "Foobar"
38
+ expect(posts.size).to eq 1
39
+ expect(posts.first.title).to eq 'Foobar'
39
40
  end
40
41
  end
41
42
 
42
- it "forwards #to_sql" do
43
- builder.to_sql.strip.should == 'SELECT "posts".* FROM "posts"'
43
+ it 'forwards #to_sql' do
44
+ expect(builder.to_sql.strip).to eq 'SELECT "posts".* FROM "posts"'
44
45
  end
45
46
 
46
- it "forwards #each" do
47
- created_post = Post.create(title: "Foobar")
47
+ it 'forwards #each' do
48
+ created_post = Post.create(title: 'Foobar')
48
49
  builder.each do |post|
49
- post.should == created_post
50
+ expect(post).to eq created_post
50
51
  end
51
52
  end
52
53
 
53
- it "forwards other enumerable methods via #each" do
54
- Post.create(title: "Foobar")
55
- builder.map(&:title).should == ["Foobar"]
54
+ it 'forwards other enumerable methods via #each' do
55
+ Post.create(title: 'Foobar')
56
+ expect(builder.map(&:title)).to eq ['Foobar']
57
+ end
58
+
59
+ it 'forwards #empty?' do
60
+ expect(builder.empty?).to eq true
61
+ end
62
+
63
+ it 'forwards #size' do
64
+ expect(builder.size).to eq 0
56
65
  end
57
66
 
58
67
  ArelHelpers::QueryBuilder::TERMINAL_METHODS.each do |method|
59
68
  it "does not enumerate records for #{method}" do
60
- mock(builder).each.never
69
+ allow(builder).to receive :each
61
70
  builder.public_send(method)
71
+ expect(builder).not_to have_received :each
62
72
  end
63
73
  end
64
74
 
65
- it "returns reflect on existing query if method returns a falsy value" do
66
- builder.optional(skip: true).to_sql.strip.should == 'SELECT "posts".* FROM "posts"'
75
+ it 'returns reflect on existing query if method returns a falsy value' do
76
+ expect(builder.optional(skip: true).to_sql.strip).to eq 'SELECT "posts".* FROM "posts"'
67
77
  end
68
78
 
69
- it "returns reflect on new query for default chainable method if value is truthy" do
70
- builder.optional(skip: false).to_sql.strip.gsub(/\s+/, " ").should == %Q{
71
- SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"title\" = 'BarBar'
72
- }.strip
79
+ it 'returns reflect on new query for default chainable method if value is truthy' do
80
+ expect(builder.optional(skip: false).to_sql.strip.gsub(/\s+/, ' ')).to eq <<-SQL.squish
81
+ SELECT "posts".* FROM "posts" WHERE "posts"."title" = 'BarBar'
82
+ SQL
73
83
  end
74
84
  end
data/spec/spec_helper.rb CHANGED
@@ -1,29 +1,20 @@
1
- # encoding: UTF-8
2
-
3
- $:.push(File.dirname(__FILE__))
1
+ $LOAD_PATH.push(File.dirname(__FILE__))
4
2
 
5
3
  require 'rspec'
6
4
  require 'arel-helpers'
7
5
  require 'fileutils'
8
6
 
9
- require 'env'
7
+ require 'combustion'
8
+ Combustion.initialize! :active_record
10
9
 
11
- def silence(&block)
12
- original_stdout = $stdout
13
- $stdout = StringIO.new
14
- begin
15
- yield
16
- ensure
17
- $stdout = original_stdout
18
- end
19
- end
10
+ require 'database_cleaner'
11
+ require 'env/models'
20
12
 
21
13
  RSpec.configure do |config|
22
- config.mock_with :rr
14
+ DatabaseCleaner.strategy = :transaction
15
+
16
+ config.before(:suite) { DatabaseCleaner.clean_with :truncation }
23
17
 
24
- config.before(:each) do
25
- ArelHelpers::Env.establish_connection
26
- ArelHelpers::Env.reset
27
- silence { ArelHelpers::Env.migrate }
28
- end
18
+ config.before { DatabaseCleaner.start }
19
+ config.after { DatabaseCleaner.clean }
29
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arel-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-21 00:00:00.000000000 Z
11
+ date: 2021-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 3.1.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '7'
22
+ version: '8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,49 +29,91 @@ dependencies:
29
29
  version: 3.1.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '7'
32
+ version: '8'
33
33
  - !ruby/object:Gem::Dependency
34
- name: rake
34
+ name: appraisal
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: combustion
35
49
  requirement: !ruby/object:Gem::Requirement
36
50
  requirements:
37
51
  - - "~>"
38
52
  - !ruby/object:Gem::Version
39
- version: '10.0'
53
+ version: '1.3'
40
54
  type: :development
41
55
  prerelease: false
42
56
  version_requirements: !ruby/object:Gem::Requirement
43
57
  requirements:
44
58
  - - "~>"
45
59
  - !ruby/object:Gem::Version
46
- version: '10.0'
60
+ version: '1.3'
61
+ - !ruby/object:Gem::Dependency
62
+ name: database_cleaner
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.8'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.8'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
47
89
  - !ruby/object:Gem::Dependency
48
90
  name: rspec
49
91
  requirement: !ruby/object:Gem::Requirement
50
92
  requirements:
51
93
  - - "~>"
52
94
  - !ruby/object:Gem::Version
53
- version: '2.11'
95
+ version: '3'
54
96
  type: :development
55
97
  prerelease: false
56
98
  version_requirements: !ruby/object:Gem::Requirement
57
99
  requirements:
58
100
  - - "~>"
59
101
  - !ruby/object:Gem::Version
60
- version: '2.11'
102
+ version: '3'
61
103
  - !ruby/object:Gem::Dependency
62
- name: rr
104
+ name: sqlite3
63
105
  requirement: !ruby/object:Gem::Requirement
64
106
  requirements:
65
107
  - - "~>"
66
108
  - !ruby/object:Gem::Version
67
- version: '1.0'
109
+ version: 1.4.0
68
110
  type: :development
69
111
  prerelease: false
70
112
  version_requirements: !ruby/object:Gem::Requirement
71
113
  requirements:
72
114
  - - "~>"
73
115
  - !ruby/object:Gem::Version
74
- version: '1.0'
116
+ version: 1.4.0
75
117
  description: Useful tools to help construct database queries with ActiveRecord and
76
118
  Arel.
77
119
  email:
@@ -93,9 +135,8 @@ files:
93
135
  - lib/arel-helpers/version.rb
94
136
  - spec/aliases_spec.rb
95
137
  - spec/arel_table_spec.rb
96
- - spec/env.rb
97
- - spec/env/migrations.rb
98
138
  - spec/env/models.rb
139
+ - spec/internal/config/database.yml
99
140
  - spec/join_association_spec.rb
100
141
  - spec/query_builder_spec.rb
101
142
  - spec/spec_helper.rb
@@ -103,7 +144,7 @@ homepage: https://github.com/camertron/arel-helpers
103
144
  licenses:
104
145
  - MIT
105
146
  metadata: {}
106
- post_install_message:
147
+ post_install_message:
107
148
  rdoc_options: []
108
149
  require_paths:
109
150
  - lib
@@ -118,8 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
159
  - !ruby/object:Gem::Version
119
160
  version: '0'
120
161
  requirements: []
121
- rubygems_version: 3.0.4
122
- signing_key:
162
+ rubygems_version: 3.2.22
163
+ signing_key:
123
164
  specification_version: 4
124
165
  summary: Useful tools to help construct database queries with ActiveRecord and Arel.
125
166
  test_files: []
@@ -1,75 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- SuperClass = if ActiveRecord::VERSION::MAJOR >= 5
4
- ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}".to_f]
5
- else
6
- ActiveRecord::Migration
7
- end
8
-
9
- class CreatePostsTable < SuperClass
10
- def change
11
- create_table :posts do |t|
12
- t.column :title, :string
13
- end
14
- end
15
- end
16
-
17
- class CreateCommentsTable < SuperClass
18
- def change
19
- create_table :comments do |t|
20
- t.references :post
21
- end
22
- end
23
- end
24
-
25
- class CreateAuthorsTable < SuperClass
26
- def change
27
- create_table :authors do |t|
28
- t.references :comment
29
- t.references :collab_posts
30
- end
31
- end
32
- end
33
-
34
- class CreateFavoritesTable < SuperClass
35
- def change
36
- create_table :favorites do |t|
37
- t.references :post
38
- end
39
- end
40
- end
41
-
42
- class CreateCollabPostsTable < SuperClass
43
- def change
44
- create_table :collab_posts do |t|
45
- t.references :authors
46
- end
47
- end
48
- end
49
-
50
- class CreateCardsTable < SuperClass
51
- def change
52
- create_table :cards
53
- end
54
- end
55
-
56
- class CreateCardLocationsTable < SuperClass
57
- def change
58
- create_table :card_locations do |t|
59
- t.references :location
60
- t.references :card, polymorphic: true
61
- end
62
- end
63
- end
64
-
65
- class CreateLocationsTable < SuperClass
66
- def change
67
- create_table :locations
68
- end
69
- end
70
-
71
- class CreateCommunityTicketsTable < SuperClass
72
- def change
73
- create_table :community_tickets
74
- end
75
- end
data/spec/env.rb DELETED
@@ -1,44 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'env/migrations'
4
- require 'env/models'
5
-
6
- module ArelHelpers
7
- class Env
8
- class << self
9
-
10
- def db_dir
11
- @db_dir ||= File.join(File.dirname(File.dirname(__FILE__)), "tmp")
12
- end
13
-
14
- def db_file
15
- @db_file ||= File.join(db_dir, "test.sqlite3")
16
- end
17
-
18
- def establish_connection
19
- ActiveRecord::Base.establish_connection(
20
- :adapter => "sqlite3",
21
- :database => db_file
22
- )
23
- end
24
-
25
- def migrate
26
- CreatePostsTable.new.change
27
- CreateCommentsTable.new.change
28
- CreateAuthorsTable.new.change
29
- CreateFavoritesTable.new.change
30
- CreateCollabPostsTable.new.change
31
- CreateCardsTable.new.change
32
- CreateCardLocationsTable.new.change
33
- CreateLocationsTable.new.change
34
- CreateCommunityTicketsTable.new.change
35
- end
36
-
37
- def reset
38
- File.unlink(db_file) if File.exist?(db_file)
39
- FileUtils.mkdir_p(db_dir)
40
- end
41
-
42
- end
43
- end
44
- end