arel-helpers 2.10.0 → 2.13.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: 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