arel-helpers 2.11.0 → 2.14.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
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