arel-helpers 2.10.0 → 2.13.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: 209526ee9962414d0eb5bab7820f6c97cfc51edf362d4ccdaf6b7309e7196fbe
4
- data.tar.gz: e3923216fd30b2ab762c581ef0056145a1b09758433159123bee1df3443bc924
3
+ metadata.gz: afe6ddeb4e41b56b59f08d6b2ac09bfd832e66b995355169b756ed22d11a2136
4
+ data.tar.gz: 944d2e1730143abe055f2dad7efe9d01b9ddafabab2080811714d72405177446
5
5
  SHA512:
6
- metadata.gz: 48814845832553b87d496b70982c46a905b0a2aecba5426d3f9e36f3fc2436cee0e71e68ab733fbc0b37cf90981515de695f309d5a4ea35d49becc83d4d1703f
7
- data.tar.gz: 638fa0c5ed41050e17bdd642fab0b4dfc94e0d3bb8084a1ca9c6cc89a9d340f11aadc0d0ba98d78628ecf4bbacd465385074cc1f0f82167f1b50298361672472
6
+ metadata.gz: 5f03ab5b864494bcc5cfb2bc7558c315a18e6460cd815c34855b2b3f0ac9d2696a49b3bd3755543c9124e2ef0667e4d774ae4164cd0317705f74556696c6cfc3
7
+ data.tar.gz: e3f704bb25be566eb5797f81804d4446b9c3c96b5df84129afb5bdeec16b91fb420d2cc7ff901648e8dab03a818d2036a5288fa6a11c95d14231b6cd9ed74b97
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
 
@@ -187,9 +187,35 @@ PostQueryBuilder.new
187
187
  .since_yesterday
188
188
  ```
189
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
+
190
216
  ## Requirements
191
217
 
192
- 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.
193
219
 
194
220
  ## Running Tests
195
221
 
data/arel-helpers.gemspec CHANGED
@@ -1,23 +1,26 @@
1
- $:.unshift File.join(File.dirname(__FILE__), 'lib')
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
2
2
  require 'arel-helpers/version'
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.name = "arel-helpers"
5
+ s.name = 'arel-helpers'
6
6
  s.version = ::ArelHelpers::VERSION
7
- s.authors = ["Cameron Dutro"]
8
- s.email = ["camertron@gmail.com"]
9
- s.homepage = "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
15
  s.add_dependency 'activerecord', '>= 3.1.0', '< 7'
16
16
 
17
+ s.add_development_dependency 'appraisal'
18
+ s.add_development_dependency 'combustion', '~> 1.3'
19
+ s.add_development_dependency 'database_cleaner', '~> 1.8'
17
20
  s.add_development_dependency 'rake', '~> 10.0'
18
- s.add_development_dependency 'rspec', '~> 2.11'
19
- s.add_development_dependency 'rr', '~> 1.0'
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']
23
26
  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,13 +9,28 @@ 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
16
16
 
17
17
  def_delegators :@query, *TERMINAL_METHODS
18
18
 
19
+ def self.not_nil(name)
20
+ mod = Module.new do
21
+ define_method(name) do |*args, **kwargs|
22
+ if (value = super(*args, **kwargs))
23
+ value
24
+ else
25
+ reflect(query)
26
+ end
27
+ end
28
+ end
29
+
30
+ prepend mod
31
+ name
32
+ end
33
+
19
34
  def initialize(query)
20
35
  @query = query
21
36
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module ArelHelpers
4
- VERSION = '2.10.0'
4
+ VERSION = '2.13.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
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ create_table :posts do |t|
5
+ t.column :title, :string
6
+ end
7
+
8
+ create_table :comments do |t|
9
+ t.references :post
10
+ end
11
+
12
+ create_table :authors do |t|
13
+ t.references :comment
14
+ t.references :collab_posts
15
+ end
16
+
17
+ create_table :favorites do |t|
18
+ t.references :post
19
+ end
20
+
21
+ create_table :collab_posts do |t|
22
+ t.references :authors
23
+ end
24
+
25
+ create_table :cards
26
+
27
+ create_table :card_locations do |t|
28
+ t.references :location
29
+ t.references :card, polymorphic: true
30
+ end
31
+
32
+ create_table :locations
33
+ create_table :community_tickets
34
+ end