squeel 1.1.1 → 1.2.1

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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +36 -4
  5. data/CHANGELOG.md +15 -0
  6. data/Gemfile +1 -1
  7. data/README.md +47 -6
  8. data/Rakefile +14 -2
  9. data/lib/squeel.rb +9 -1
  10. data/lib/squeel/adapters/active_record.rb +0 -1
  11. data/lib/squeel/adapters/active_record/3.0/join_dependency_extensions.rb +1 -0
  12. data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +12 -1
  13. data/lib/squeel/adapters/active_record/3.1/join_dependency_extensions.rb +1 -0
  14. data/lib/squeel/adapters/active_record/3.2/join_dependency_extensions.rb +1 -0
  15. data/lib/squeel/adapters/active_record/4.0/join_dependency_extensions.rb +1 -0
  16. data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +92 -0
  17. data/lib/squeel/adapters/active_record/4.1/compat.rb +15 -0
  18. data/lib/squeel/adapters/active_record/4.1/context.rb +88 -0
  19. data/lib/squeel/adapters/active_record/4.1/preloader_extensions.rb +31 -0
  20. data/lib/squeel/adapters/active_record/4.1/reflection_extensions.rb +37 -0
  21. data/lib/squeel/adapters/active_record/4.1/relation_extensions.rb +307 -0
  22. data/lib/squeel/adapters/active_record/4.2/compat.rb +1 -0
  23. data/lib/squeel/adapters/active_record/4.2/context.rb +1 -0
  24. data/lib/squeel/adapters/active_record/4.2/preloader_extensions.rb +1 -0
  25. data/lib/squeel/adapters/active_record/4.2/relation_extensions.rb +108 -0
  26. data/lib/squeel/adapters/active_record/context.rb +7 -7
  27. data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +9 -13
  28. data/lib/squeel/adapters/active_record/relation_extensions.rb +38 -8
  29. data/lib/squeel/core_ext/symbol.rb +3 -3
  30. data/lib/squeel/dsl.rb +1 -1
  31. data/lib/squeel/nodes.rb +1 -0
  32. data/lib/squeel/nodes/as.rb +12 -0
  33. data/lib/squeel/nodes/join.rb +8 -4
  34. data/lib/squeel/nodes/key_path.rb +10 -1
  35. data/lib/squeel/nodes/node.rb +21 -0
  36. data/lib/squeel/nodes/stub.rb +8 -4
  37. data/lib/squeel/nodes/subquery_join.rb +44 -0
  38. data/lib/squeel/version.rb +1 -1
  39. data/lib/squeel/visitors.rb +2 -0
  40. data/lib/squeel/visitors/enumeration_visitor.rb +101 -0
  41. data/lib/squeel/visitors/order_visitor.rb +9 -2
  42. data/lib/squeel/visitors/predicate_visitor.rb +11 -0
  43. data/lib/squeel/visitors/preload_visitor.rb +12 -0
  44. data/lib/squeel/visitors/visitor.rb +89 -13
  45. data/spec/config.travis.yml +13 -0
  46. data/spec/config.yml +12 -0
  47. data/spec/console.rb +3 -12
  48. data/spec/core_ext/symbol_spec.rb +3 -3
  49. data/spec/helpers/squeel_helper.rb +8 -5
  50. data/spec/spec_helper.rb +4 -16
  51. data/spec/squeel/adapters/active_record/context_spec.rb +8 -4
  52. data/spec/squeel/adapters/active_record/join_dependency_extensions_spec.rb +123 -38
  53. data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +350 -124
  54. data/spec/squeel/core_ext/symbol_spec.rb +3 -3
  55. data/spec/squeel/nodes/join_spec.rb +4 -4
  56. data/spec/squeel/nodes/stub_spec.rb +3 -3
  57. data/spec/squeel/nodes/subquery_join_spec.rb +46 -0
  58. data/spec/squeel/visitors/order_visitor_spec.rb +3 -3
  59. data/spec/squeel/visitors/predicate_visitor_spec.rb +69 -36
  60. data/spec/squeel/visitors/select_visitor_spec.rb +1 -1
  61. data/spec/squeel/visitors/visitor_spec.rb +7 -6
  62. data/spec/support/models.rb +99 -15
  63. data/spec/support/schema.rb +109 -4
  64. data/squeel.gemspec +8 -6
  65. metadata +89 -107
  66. data/.ruby-gemset +0 -1
  67. data/.ruby-version +0 -1
  68. data/spec/blueprints/articles.rb +0 -5
  69. data/spec/blueprints/comments.rb +0 -5
  70. data/spec/blueprints/notes.rb +0 -3
  71. data/spec/blueprints/people.rb +0 -4
  72. data/spec/blueprints/tags.rb +0 -3
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3d3196690094de459e3e30179f68ce63ee2032e1
4
+ data.tar.gz: 827a3e061d72e29cac9b615cbc3fb66c6c1e3462
5
+ SHA512:
6
+ metadata.gz: fcf69aceda5afe483dfad4a91d40b21d8472b250c5a9eb719afaa6ccc1252d9488d176c0cc9d41d7a68e18a66f0dd5ff33dbcf4d77b59fd12c673e311cae680c
7
+ data.tar.gz: 6a934cabda1f179d9a920cf4974fe8776b68d4a58578ee2c067ae4f1e7fac78a0989268920e711845d78367d66521c297e0cb8215e5d8fe85b336d275ae0d249
data/.gitignore CHANGED
@@ -3,3 +3,6 @@
3
3
  Gemfile.lock
4
4
  pkg/*
5
5
  .rvmrc
6
+ .ruby-version
7
+ .ruby-gemset
8
+ script
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --backtrace
@@ -1,8 +1,40 @@
1
+ before_script:
2
+ - mysql -e "create database squeel_test_examples;"
3
+ - psql -c "create database squeel_test_examples;" -U postgres
4
+
5
+ script:
6
+ - bundle exec rake test:$ADAPTER
7
+
1
8
  rvm:
2
9
  - 1.9.3
10
+ - 2.0.0
11
+ - 2.1.1
12
+ - 2.1.2
3
13
 
4
14
  env:
5
- - RAILS=4-0-stable AREL=master
6
- - RAILS=3-2-stable AREL=3-0-stable
7
- - RAILS=3-1-stable AREL=2-2-stable
8
- - RAILS=3-0-stable AREL=2-0-stable
15
+ global:
16
+ - SQ_CONFIG_FILE=$TRAVIS_BUILD_DIR/spec/config.travis.yml
17
+ matrix:
18
+ - RAILS=master AREL=master ADAPTER=sqlite3
19
+ - RAILS=4-1-stable AREL=5-0-stable ADAPTER=sqlite3
20
+ - RAILS=4-0-stable AREL=4-0-stable ADAPTER=sqlite3
21
+ - RAILS=3-2-stable AREL=3-0-stable ADAPTER=sqlite3
22
+ - RAILS=3-1-stable AREL=2-2-stable ADAPTER=sqlite3
23
+ - RAILS=3-0-stable AREL=2-0-stable ADAPTER=sqlite3
24
+ - RAILS=master AREL=master ADAPTER=mysql
25
+ - RAILS=4-1-stable AREL=5-0-stable ADAPTER=mysql
26
+ - RAILS=4-0-stable AREL=4-0-stable ADAPTER=mysql
27
+ - RAILS=3-2-stable AREL=3-0-stable ADAPTER=mysql
28
+ - RAILS=3-1-stable AREL=2-2-stable ADAPTER=mysql
29
+ - RAILS=3-0-stable AREL=2-0-stable ADAPTER=mysql
30
+ - RAILS=master AREL=master ADAPTER=mysql2
31
+ - RAILS=4-1-stable AREL=5-0-stable ADAPTER=mysql2
32
+ - RAILS=4-0-stable AREL=4-0-stable ADAPTER=mysql2
33
+ - RAILS=3-2-stable AREL=3-0-stable ADAPTER=mysql2
34
+ - RAILS=3-1-stable AREL=2-2-stable ADAPTER=mysql2
35
+ - RAILS=master AREL=master ADAPTER=postgresql
36
+ - RAILS=4-1-stable AREL=5-0-stable ADAPTER=postgresql
37
+ - RAILS=4-0-stable AREL=4-0-stable ADAPTER=postgresql
38
+ - RAILS=3-2-stable AREL=3-0-stable ADAPTER=postgresql
39
+ - RAILS=3-1-stable AREL=2-2-stable ADAPTER=postgresql
40
+ - RAILS=3-0-stable AREL=2-0-stable ADAPTER=postgresql
@@ -1,3 +1,18 @@
1
+ ## 1.2.1 (Unreleased)
2
+
3
+ * Run all specs against sqlite, mysql and postgresql!
4
+ * Genereta table names correctly when joining through an association. Fixes#302.
5
+ * Enable Arel nodes in Squeel with "|" operator. Fixes #314.
6
+ * Properly append binds from a relation used in a subquery. Fixes #272.
7
+
8
+ ## 1.2.0 (2014-07-16)
9
+
10
+ * Add compatibility to Ruby 2.0+ with Rails 4.1 and 4.2.0.alpha.
11
+ Fixes #301, #305, #307
12
+ * Enable using a relation as a subquery. Fixes #309
13
+ * Bind params correctly in subquery using associations. Fixes #312
14
+ * Use the correct attribute name when finding a Join node. Fixes #273.
15
+
1
16
  ## 1.1.1 (2013-09-03)
2
17
 
3
18
  * Update relation extensions to support new count behavior in Active Record
data/Gemfile CHANGED
@@ -3,7 +3,7 @@ gemspec
3
3
 
4
4
  gem 'rake'
5
5
 
6
- rails = ENV['RAILS'] || '4-0-stable'
6
+ rails = ENV['RAILS'] || 'master'
7
7
  arel = ENV['AREL'] || 'master'
8
8
 
9
9
  arel_opts = case arel
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Squeel [![Build Status](https://secure.travis-ci.org/ernie/squeel.png)](http://travis-ci.org/ernie/squeel) [![endorse](http://api.coderwall.com/ernie/endorsecount.png)](http://coderwall.com/ernie)
1
+ # Squeel [![Build Status](https://secure.travis-ci.org/activerecord-hackery/squeel.png)](http://travis-ci.org/activerecord-hackery/squeel) [![endorse](http://api.coderwall.com/ernie/endorsecount.png)](http://coderwall.com/ernie)
2
2
 
3
3
  Squeel lets you write your Active Record queries with fewer strings, and more Ruby,
4
4
  by making the Arel awesomeness that lies beneath Active Record more accessible.
@@ -23,8 +23,10 @@ just a simple example -- Squeel's capable of a whole lot more. Keep reading.
23
23
  In your Gemfile:
24
24
 
25
25
  ```ruby
26
+ # Make sure you are using the latest version of polyamorous
27
+ gem "polyamorous", :git => "git://github.com/activerecord-hackery/polyamorous.git"
26
28
  gem "squeel" # Last officially released gem
27
- # gem "squeel", :git => "git://github.com/ernie/squeel.git" # Track git repo
29
+ # gem "squeel", :git => "git://github.com/activerecord-hackery/squeel.git" # Track git repo
28
30
  ```
29
31
 
30
32
  Then bundle as usual.
@@ -86,6 +88,24 @@ A Squeel keypath is essentially a more concise and readable alternative to a
86
88
  deeply nested hash. For instance, in standard Active Record, you might join several
87
89
  associations like this to perform a query:
88
90
 
91
+ #### Rails 4+
92
+
93
+ ```ruby
94
+ Person.joins(:articles => {:comments => :person}).references(:all)
95
+ # => SELECT "people".* FROM "people"
96
+ # LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
97
+ # LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
98
+ # LEFT OUTER JOIN "people" "people_comments" ON "people_comments"."id" = "comments"."person_id"
99
+ ```
100
+
101
+ With a keypath, this would look like:
102
+
103
+ ```ruby
104
+ Person.joins{articles.comments.person}.references(:all)
105
+ ```
106
+
107
+ #### Rails 3.x
108
+
89
109
  ```ruby
90
110
  Person.joins(:articles => {:comments => :person})
91
111
  # => SELECT "people".* FROM "people"
@@ -413,6 +433,27 @@ Person.joins{children.parent.children}.
413
433
 
414
434
  Keypaths were used here for clarity, but nested hashes would work just as well.
415
435
 
436
+ You can also use a subquery in a join.
437
+
438
+ Notice:
439
+ 1. Squeel can only accept an ActiveRecord::Relation class of subqueries in a join.
440
+ 2. Use the chain with caution. You should call `as` first to get a Nodes::As, then call `on` to get a join node.
441
+
442
+ ```ruby
443
+ subquery = OrderItem.group(:orderable_id).select { [orderable_id, sum(quantity * unit_price).as(amount)] }
444
+ Seat.joins { [payment.outer, subquery.as('seat_order_items').on { id == seat_order_items.orderable_id}.outer] }.
445
+ select { [seat_order_items.amount, "seats.*"] }
446
+ # => SELECT "seat_order_items"."amount", seats.*
447
+ # FROM "seats"
448
+ # LEFT OUTER JOIN "payments" ON "payments"."id" = "seats"."payment_id"
449
+ # LEFT OUTER JOIN (
450
+ # SELECT "order_items"."orderable_id",
451
+ # sum("order_items"."quantity" * "order_items"."unit_price") AS amount
452
+ # FROM "order_items"
453
+ # GROUP BY "order_items"."orderable_id"
454
+ # ) seat_order_items ON "seats"."id" = "seat_order_items"."orderable_id"
455
+ ```
456
+
416
457
  ### Functions
417
458
 
418
459
  You can call SQL functions just like you would call a method in Ruby...
@@ -470,7 +511,7 @@ As you can see, just like functions, these operations can be given aliases.
470
511
  To select more than one attribute (or calculated attribute) simply put them into an array:
471
512
 
472
513
  ```ruby
473
- p = Person.select{[ name.op('||', '-diddly').as(flanderized_name),
514
+ p = Person.select{[ name.op('||', '-diddly').as(flanderized_name),
474
515
  coalesce(name, '<no name given>').as(name_with_default) ]}.first
475
516
  p.flanderized_name
476
517
  # => "Aric Smith-diddly"
@@ -537,9 +578,9 @@ For further information, see
537
578
  [this post to the Rails list](https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-core/NQJJzZ7R7S0),
538
579
  [this commit](https://github.com/lifo/docrails/commit/50c5005bafe7e43f81a141cd2c512379aec74325) to
539
580
  the [Active Record guides](http://edgeguides.rubyonrails.org/active_record_querying.html#hash-conditions),
540
- [#67](https://github.com/ernie/squeel/issues/67),
541
- [#75](https://github.com/ernie/squeel/issues/75), and
542
- [#171](https://github.com/ernie/squeel/issues/171).
581
+ [#67](https://github.com/activerecord-hackery/squeel/issues/67),
582
+ [#75](https://github.com/activerecord-hackery/squeel/issues/75), and
583
+ [#171](https://github.com/activerecord-hackery/squeel/issues/171).
543
584
 
544
585
  ## Compatibility with MetaWhere
545
586
 
data/Rakefile CHANGED
@@ -4,10 +4,22 @@ require 'rspec/core/rake_task'
4
4
  Bundler::GemHelper.install_tasks
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec) do |rspec|
7
- rspec.rspec_opts = ['--backtrace']
7
+ rspec.rspec_opts = ['--backtrace', '--color', '--format documentation']
8
8
  end
9
9
 
10
- task :default => :spec
10
+ task :default => 'test_sqlite3'
11
+
12
+ %w(sqlite3 mysql mysql2 postgresql).each do |adapter|
13
+ namespace :test do
14
+ task(adapter => ["#{adapter}:env", "spec"])
15
+ end
16
+
17
+ namespace adapter do
18
+ task(:env) { ENV['SQ_DB'] = adapter }
19
+ end
20
+
21
+ task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"]
22
+ end
11
23
 
12
24
  desc "Open an irb session with Squeel and the sample data used in specs"
13
25
  task :console do
@@ -1,7 +1,16 @@
1
1
  require 'squeel/configuration'
2
+ require 'polyamorous'
2
3
 
3
4
  module Squeel
4
5
 
6
+ if defined?(Arel::InnerJoin)
7
+ InnerJoin = Arel::InnerJoin
8
+ OuterJoin = Arel::OuterJoin
9
+ else
10
+ InnerJoin = Arel::Nodes::InnerJoin
11
+ OuterJoin = Arel::Nodes::OuterJoin
12
+ end
13
+
5
14
  extend Configuration
6
15
 
7
16
  # Prevent warnings on the console when doing things some might describe as "evil"
@@ -30,7 +39,6 @@ module Squeel
30
39
  alias_predicate aliaz, original
31
40
  end
32
41
  end
33
-
34
42
  end
35
43
 
36
44
  require 'squeel/dsl'
@@ -1,7 +1,6 @@
1
1
  case ActiveRecord::VERSION::MAJOR
2
2
  when 3, 4
3
3
  ActiveRecord::Relation.send :include, Squeel::Nodes::Aliasing
4
- require 'squeel/adapters/active_record/join_dependency_extensions'
5
4
  require 'squeel/adapters/active_record/base_extensions'
6
5
 
7
6
  adapter_directory = "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/join_dependency_extensions'
@@ -54,8 +54,9 @@ module Squeel
54
54
  end
55
55
 
56
56
  stashed_association_joins = joins.grep(::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
57
+ subquery_joins = joins.grep(Nodes::SubqueryJoin)
57
58
 
58
- non_association_joins = (joins - association_joins - stashed_association_joins)
59
+ non_association_joins = (joins - association_joins - stashed_association_joins - subquery_joins)
59
60
  custom_joins = custom_join_sql(*non_association_joins)
60
61
 
61
62
  self.join_dependency = JoinDependency.new(@klass, association_joins, custom_joins)
@@ -79,6 +80,16 @@ module Squeel
79
80
  relation = relation.join(left, join_type).on(*right)
80
81
  end
81
82
 
83
+ subquery_joins.each do |join|
84
+ relation = relation.
85
+ join(
86
+ Arel::Nodes::TableAlias.new(
87
+ join.subquery.right,
88
+ Arel::Nodes::Grouping.new(join.subquery.left.arel.ast)),
89
+ join.type).
90
+ on(*where_visit(join.constraints))
91
+ end
92
+
82
93
  relation = relation.join(custom_joins)
83
94
  end
84
95
 
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/join_dependency_extensions'
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/join_dependency_extensions'
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/join_dependency_extensions'
@@ -30,6 +30,24 @@ module Squeel
30
30
  end
31
31
  end
32
32
 
33
+ def where_unscoping(target_value)
34
+ target_value = target_value.to_s
35
+
36
+ where_values.reject! do |rel|
37
+ case rel
38
+ when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
39
+ subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
40
+ subrelation.name == target_value
41
+ when Hash
42
+ rel.stringify_keys.has_key?(target_value)
43
+ when Squeel::Nodes::Predicate
44
+ rel.expr.symbol.to_s == target_value
45
+ end
46
+ end
47
+
48
+ bind_values.reject! { |col,_| col.name == target_value }
49
+ end
50
+
33
51
  def build_arel
34
52
  arel = Arel::SelectManager.new(table.engine, table)
35
53
 
@@ -52,9 +70,49 @@ module Squeel
52
70
  arel.from(build_from) if from_value
53
71
  arel.lock(lock_value) if lock_value
54
72
 
73
+ # Reorder bind indexes when joins or subqueries include more bindings.
74
+ # Special for PostgreSQL
75
+ if bind_values.size > 1
76
+ bvs = bind_values
77
+ arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
78
+ column = bvs[i].first
79
+ bp.replace connection.substitute_at(column, i)
80
+ end
81
+ end
82
+
55
83
  arel
56
84
  end
57
85
 
86
+ def build_where(opts, other = [])
87
+ case opts
88
+ when String, Array
89
+ super
90
+ else # Let's prevent PredicateBuilder from doing its thing
91
+ [opts, *other].map do |arg|
92
+ case arg
93
+ when Array # Just in case there's an array in there somewhere
94
+ @klass.send(:sanitize_sql, arg)
95
+ when Hash
96
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, arg)
97
+
98
+ attributes.values.grep(::ActiveRecord::Relation) do |rel|
99
+ self.bind_values += rel.bind_values
100
+ end
101
+
102
+ preprocess_attrs_with_ar(attributes)
103
+
104
+ when Squeel::Nodes::Node
105
+ arg.grep(::ActiveRecord::Relation) do |rel|
106
+ self.bind_values += rel.bind_values
107
+ end
108
+ arg
109
+ else
110
+ arg
111
+ end
112
+ end
113
+ end
114
+ end
115
+
58
116
  def build_from
59
117
  opts, name = from_visit(from_value)
60
118
  case opts
@@ -109,6 +167,40 @@ module Squeel
109
167
  self
110
168
  end
111
169
 
170
+ def where_values_hash_with_squeel(relation_table_name = table_name)
171
+ equalities = find_equality_predicates(where_visit(with_default_scope.where_values), relation_table_name)
172
+
173
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
174
+
175
+ Hash[equalities.map { |where|
176
+ name = where.left.name
177
+ [name, binds.fetch(name.to_s) { where.right }]
178
+ }]
179
+ end
180
+
181
+ def to_sql_with_binding_params
182
+ @to_sql ||= begin
183
+ relation = self
184
+ connection = klass.connection
185
+
186
+ if eager_loading?
187
+ find_with_associations { |rel| relation = rel }
188
+ end
189
+
190
+ ast = relation.arel.ast
191
+ binds = relation.bind_values.dup
192
+
193
+ visitor = connection.visitor.clone
194
+ visitor.class_eval do
195
+ include ::Arel::Visitors::BindVisitor
196
+ end
197
+
198
+ visitor.accept(ast) do
199
+ connection.quote(*binds.shift.reverse)
200
+ end
201
+ end
202
+ end
203
+
112
204
  private
113
205
 
114
206
  def dehashified_order_values
@@ -0,0 +1,15 @@
1
+ require 'squeel/adapters/active_record/compat'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class AssociationScope
6
+ def eval_scope(klass, scope, owner)
7
+ if scope.is_a?(Relation)
8
+ scope
9
+ else
10
+ klass.unscoped.instance_exec(owner, &scope).visited
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,88 @@
1
+ require 'squeel/adapters/active_record/context'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ class Context < ::Squeel::Context
7
+ class NoParentFoundError < RuntimeError; end
8
+
9
+ def initialize(object)
10
+ super
11
+ @base = object.join_root
12
+ @engine = @base.base_klass.arel_engine
13
+ @arel_visitor = get_arel_visitor
14
+ @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
15
+ end
16
+
17
+ def find(object, parent = @base)
18
+ if ::ActiveRecord::Associations::JoinDependency::JoinPart === parent
19
+ case object
20
+ when String, Symbol, Nodes::Stub
21
+ assoc_name = object.to_s
22
+ find_string_symbol_stub_association(@base.children, @base, assoc_name, parent)
23
+ when Nodes::Join
24
+ find_node_join_association(@base.children, @base, object, parent)
25
+ else
26
+ find_other_association(@base.children, @base, object, parent)
27
+ end
28
+ end
29
+ end
30
+
31
+ def find!(object, parent = @base)
32
+ if ::ActiveRecord::Associations::JoinDependency::JoinPart === parent
33
+ result =
34
+ case object
35
+ when String, Symbol, Nodes::Stub
36
+ assoc_name = object.to_s
37
+ find_string_symbol_stub_association(@base.children, @base, assoc_name, parent)
38
+ when Nodes::Join
39
+ find_node_join_association(@base.children, @base, object, parent)
40
+ else
41
+ find_other_association(@base.children, @base, object, parent)
42
+ end
43
+
44
+ result || raise(NoParentFoundError, "can't find #{object} in #{parent}")
45
+ else
46
+ raise NoParentFoundError, "can't find #{object} in #{parent}"
47
+ end
48
+ end
49
+
50
+ def traverse!(keypath, parent = @base, include_endpoint = false)
51
+ parent = @base if keypath.absolute?
52
+ keypath.path_without_endpoint.each do |key|
53
+ parent = find!(key, parent)
54
+ end
55
+ parent = find!(keypath.endpoint, parent) if include_endpoint
56
+
57
+ parent
58
+ end
59
+
60
+ private
61
+ def find_string_symbol_stub_association(join_associations, current_parent, assoc_name, target_parent)
62
+ join_associations.each do |assoc|
63
+ return assoc if assoc.reflection.name.to_s == assoc_name && current_parent.equal?(target_parent)
64
+ child_assoc = find_string_symbol_stub_association(assoc.children, assoc, assoc_name, target_parent)
65
+ return child_assoc if child_assoc
66
+ end && false
67
+ end
68
+
69
+ def find_node_join_association(join_associations, current_parent, object, target_parent)
70
+ join_associations.each do |assoc|
71
+ return assoc if assoc.reflection.name == object._name && current_parent.equal?(target_parent) &&
72
+ (object.polymorphic? ? assoc.reflection.klass == object._klass : true)
73
+ child_assoc = find_node_join_association(assoc.children, assoc, object, target_parent)
74
+ return child_assoc if child_assoc
75
+ end && false
76
+ end
77
+
78
+ def find_other_association(join_associations, current_parent, object, target_parent)
79
+ join_associations.each do |assoc|
80
+ return assoc if assoc.reflection == object && current_parent.equal?(target_parent)
81
+ child_assoc = find_other_association(assoc.children, assoc, object, target_parent)
82
+ return child_assoc if child_assoc
83
+ end && false
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end