giga-pure-kit 0.0.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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/giga-pure-kit.gemspec +12 -0
  3. data/ransack-4.4.1/LICENSE +20 -0
  4. data/ransack-4.4.1/README.md +101 -0
  5. data/ransack-4.4.1/lib/polyamorous/activerecord/join_association.rb +218 -0
  6. data/ransack-4.4.1/lib/polyamorous/activerecord/join_association_7_2.rb +55 -0
  7. data/ransack-4.4.1/lib/polyamorous/activerecord/join_dependency.rb +102 -0
  8. data/ransack-4.4.1/lib/polyamorous/activerecord/reflection.rb +11 -0
  9. data/ransack-4.4.1/lib/polyamorous/join.rb +70 -0
  10. data/ransack-4.4.1/lib/polyamorous/polyamorous.rb +27 -0
  11. data/ransack-4.4.1/lib/polyamorous/swapping_reflection_class.rb +11 -0
  12. data/ransack-4.4.1/lib/polyamorous/tree_node.rb +7 -0
  13. data/ransack-4.4.1/lib/ransack/active_record.rb +14 -0
  14. data/ransack-4.4.1/lib/ransack/adapters/active_record/base.rb +152 -0
  15. data/ransack-4.4.1/lib/ransack/adapters/active_record/context.rb +373 -0
  16. data/ransack-4.4.1/lib/ransack/configuration.rb +209 -0
  17. data/ransack-4.4.1/lib/ransack/constants.rb +176 -0
  18. data/ransack-4.4.1/lib/ransack/context.rb +190 -0
  19. data/ransack-4.4.1/lib/ransack/helpers/form_builder.rb +277 -0
  20. data/ransack-4.4.1/lib/ransack/helpers/form_helper.rb +311 -0
  21. data/ransack-4.4.1/lib/ransack/helpers.rb +2 -0
  22. data/ransack-4.4.1/lib/ransack/invalid_search_error.rb +3 -0
  23. data/ransack-4.4.1/lib/ransack/locale/ar.yml +70 -0
  24. data/ransack-4.4.1/lib/ransack/locale/az.yml +70 -0
  25. data/ransack-4.4.1/lib/ransack/locale/bg.yml +70 -0
  26. data/ransack-4.4.1/lib/ransack/locale/ca.yml +70 -0
  27. data/ransack-4.4.1/lib/ransack/locale/cs.yml +70 -0
  28. data/ransack-4.4.1/lib/ransack/locale/da.yml +70 -0
  29. data/ransack-4.4.1/lib/ransack/locale/de.yml +70 -0
  30. data/ransack-4.4.1/lib/ransack/locale/el.yml +70 -0
  31. data/ransack-4.4.1/lib/ransack/locale/en.yml +70 -0
  32. data/ransack-4.4.1/lib/ransack/locale/es.yml +70 -0
  33. data/ransack-4.4.1/lib/ransack/locale/fa.yml +70 -0
  34. data/ransack-4.4.1/lib/ransack/locale/fi.yml +71 -0
  35. data/ransack-4.4.1/lib/ransack/locale/fr.yml +70 -0
  36. data/ransack-4.4.1/lib/ransack/locale/hu.yml +70 -0
  37. data/ransack-4.4.1/lib/ransack/locale/id.yml +70 -0
  38. data/ransack-4.4.1/lib/ransack/locale/it.yml +70 -0
  39. data/ransack-4.4.1/lib/ransack/locale/ja.yml +70 -0
  40. data/ransack-4.4.1/lib/ransack/locale/ko.yml +70 -0
  41. data/ransack-4.4.1/lib/ransack/locale/nl.yml +70 -0
  42. data/ransack-4.4.1/lib/ransack/locale/pt-BR.yml +70 -0
  43. data/ransack-4.4.1/lib/ransack/locale/ro.yml +70 -0
  44. data/ransack-4.4.1/lib/ransack/locale/ru.yml +70 -0
  45. data/ransack-4.4.1/lib/ransack/locale/sk.yml +70 -0
  46. data/ransack-4.4.1/lib/ransack/locale/sv.yml +70 -0
  47. data/ransack-4.4.1/lib/ransack/locale/tr.yml +70 -0
  48. data/ransack-4.4.1/lib/ransack/locale/uk.yml +72 -0
  49. data/ransack-4.4.1/lib/ransack/locale/zh-CN.yml +70 -0
  50. data/ransack-4.4.1/lib/ransack/locale/zh-TW.yml +70 -0
  51. data/ransack-4.4.1/lib/ransack/naming.rb +58 -0
  52. data/ransack-4.4.1/lib/ransack/nodes/attribute.rb +59 -0
  53. data/ransack-4.4.1/lib/ransack/nodes/bindable.rb +52 -0
  54. data/ransack-4.4.1/lib/ransack/nodes/condition.rb +399 -0
  55. data/ransack-4.4.1/lib/ransack/nodes/grouping.rb +206 -0
  56. data/ransack-4.4.1/lib/ransack/nodes/node.rb +34 -0
  57. data/ransack-4.4.1/lib/ransack/nodes/sort.rb +54 -0
  58. data/ransack-4.4.1/lib/ransack/nodes/value.rb +115 -0
  59. data/ransack-4.4.1/lib/ransack/predicate.rb +75 -0
  60. data/ransack-4.4.1/lib/ransack/ransacker.rb +24 -0
  61. data/ransack-4.4.1/lib/ransack/search.rb +195 -0
  62. data/ransack-4.4.1/lib/ransack/translate.rb +156 -0
  63. data/ransack-4.4.1/lib/ransack/version.rb +3 -0
  64. data/ransack-4.4.1/lib/ransack/visitor.rb +87 -0
  65. data/ransack-4.4.1/lib/ransack.rb +33 -0
  66. data/ransack-4.4.1/spec/console.rb +13 -0
  67. data/ransack-4.4.1/spec/factories/articles.rb +7 -0
  68. data/ransack-4.4.1/spec/factories/comments.rb +7 -0
  69. data/ransack-4.4.1/spec/factories/notes.rb +13 -0
  70. data/ransack-4.4.1/spec/factories/people.rb +10 -0
  71. data/ransack-4.4.1/spec/factories/tags.rb +5 -0
  72. data/ransack-4.4.1/spec/helpers/polyamorous_helper.rb +13 -0
  73. data/ransack-4.4.1/spec/helpers/ransack_helper.rb +9 -0
  74. data/ransack-4.4.1/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  75. data/ransack-4.4.1/spec/polyamorous/join_association_spec.rb +29 -0
  76. data/ransack-4.4.1/spec/polyamorous/join_dependency_spec.rb +80 -0
  77. data/ransack-4.4.1/spec/polyamorous/join_spec.rb +19 -0
  78. data/ransack-4.4.1/spec/ransack/adapters/active_record/base_spec.rb +943 -0
  79. data/ransack-4.4.1/spec/ransack/adapters/active_record/context_spec.rb +219 -0
  80. data/ransack-4.4.1/spec/ransack/configuration_spec.rb +201 -0
  81. data/ransack-4.4.1/spec/ransack/helpers/form_builder_spec.rb +169 -0
  82. data/ransack-4.4.1/spec/ransack/helpers/form_helper_spec.rb +1075 -0
  83. data/ransack-4.4.1/spec/ransack/invalid_search_error_spec.rb +27 -0
  84. data/ransack-4.4.1/spec/ransack/nodes/condition_spec.rb +333 -0
  85. data/ransack-4.4.1/spec/ransack/nodes/grouping_spec.rb +111 -0
  86. data/ransack-4.4.1/spec/ransack/nodes/value_spec.rb +126 -0
  87. data/ransack-4.4.1/spec/ransack/predicate_spec.rb +504 -0
  88. data/ransack-4.4.1/spec/ransack/ransacker_spec.rb +69 -0
  89. data/ransack-4.4.1/spec/ransack/search_spec.rb +821 -0
  90. data/ransack-4.4.1/spec/ransack/translate_spec.rb +16 -0
  91. data/ransack-4.4.1/spec/spec_helper.rb +52 -0
  92. data/ransack-4.4.1/spec/support/en.yml +18 -0
  93. data/ransack-4.4.1/spec/support/schema.rb +419 -0
  94. metadata +133 -0
@@ -0,0 +1,11 @@
1
+ module Polyamorous
2
+ module SwappingReflectionClass
3
+ def swapping_reflection_klass(reflection, klass)
4
+ new_reflection = reflection.clone
5
+ new_reflection.instance_variable_set(:@options, reflection.options.clone)
6
+ new_reflection.options.delete(:polymorphic)
7
+ new_reflection.instance_variable_set(:@klass, klass)
8
+ yield new_reflection
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Polyamorous
2
+ module TreeNode
3
+ def add_to_tree(hash)
4
+ raise NotImplementedError
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'ransack/adapters/active_record/base'
2
+
3
+ ActiveSupport.on_load(:active_record) do
4
+ extend Ransack::Adapters::ActiveRecord::Base
5
+
6
+ Ransack::SUPPORTS_ATTRIBUTE_ALIAS =
7
+ begin
8
+ ActiveRecord::Base.respond_to?(:attribute_aliases)
9
+ rescue NameError
10
+ false
11
+ end
12
+ end
13
+
14
+ require 'ransack/adapters/active_record/context'
@@ -0,0 +1,152 @@
1
+ module Ransack
2
+ module Adapters
3
+ module ActiveRecord
4
+ module Base
5
+
6
+ def self.extended(base)
7
+ base.class_eval do
8
+ class_attribute :_ransackers
9
+ class_attribute :_ransack_aliases
10
+ self._ransackers ||= {}
11
+ self._ransack_aliases ||= {}
12
+ end
13
+ end
14
+
15
+ def ransack(params = {}, options = {})
16
+ Search.new(self, params, options)
17
+ end
18
+
19
+ def ransack!(params = {}, options = {})
20
+ ransack(params, options.merge(ignore_unknown_conditions: false))
21
+ end
22
+
23
+ def ransacker(name, opts = {}, &block)
24
+ self._ransackers = _ransackers.merge name.to_s => Ransacker
25
+ .new(self, name, opts, &block)
26
+ end
27
+
28
+ def ransack_alias(new_name, old_name)
29
+ self._ransack_aliases = _ransack_aliases.merge new_name.to_s =>
30
+ old_name.to_s
31
+ end
32
+
33
+ # Ransackable_attributes, by default, returns all column names
34
+ # and any defined ransackers as an array of strings.
35
+ # For overriding with a whitelist array of strings.
36
+ #
37
+ def ransackable_attributes(auth_object = nil)
38
+ @ransackable_attributes ||= deprecated_ransackable_list(:ransackable_attributes)
39
+ end
40
+
41
+ # Ransackable_associations, by default, returns the names
42
+ # of all associations as an array of strings.
43
+ # For overriding with a whitelist array of strings.
44
+ #
45
+ def ransackable_associations(auth_object = nil)
46
+ @ransackable_associations ||= deprecated_ransackable_list(:ransackable_associations)
47
+ end
48
+
49
+ # Ransortable_attributes, by default, returns the names
50
+ # of all attributes available for sorting as an array of strings.
51
+ # For overriding with a whitelist array of strings.
52
+ #
53
+ def ransortable_attributes(auth_object = nil)
54
+ ransackable_attributes(auth_object)
55
+ end
56
+
57
+ # Ransackable_scopes, by default, returns an empty array
58
+ # i.e. no class methods/scopes are authorized.
59
+ # For overriding with a whitelist array of *symbols*.
60
+ #
61
+ def ransackable_scopes(auth_object = nil)
62
+ []
63
+ end
64
+
65
+ # ransack_scope_skip_sanitize_args, by default, returns an empty array.
66
+ # i.e. use the sanitize_scope_args setting to determine if args should be converted.
67
+ # For overriding with a list of scopes which should be passed the args as-is.
68
+ #
69
+ def ransackable_scopes_skip_sanitize_args
70
+ []
71
+ end
72
+
73
+ # Bare list of all potentially searchable attributes. Searchable attributes
74
+ # need to be explicitly allowlisted through the `ransackable_attributes`
75
+ # method in each model, but if you're allowing almost everything to be
76
+ # searched, this list can be used as a base for exclusions.
77
+ #
78
+ def authorizable_ransackable_attributes
79
+ if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
80
+ column_names + _ransackers.keys + _ransack_aliases.keys +
81
+ attribute_aliases.keys
82
+ else
83
+ column_names + _ransackers.keys + _ransack_aliases.keys
84
+ end.uniq
85
+ end
86
+
87
+ # Bare list of all potentially searchable associations. Searchable
88
+ # associations need to be explicitly allowlisted through the
89
+ # `ransackable_associations` method in each model, but if you're
90
+ # allowing almost everything to be searched, this list can be used as a
91
+ # base for exclusions.
92
+ #
93
+ def authorizable_ransackable_associations
94
+ reflect_on_all_associations.map { |a| a.name.to_s }
95
+ end
96
+
97
+ private
98
+
99
+ def deprecated_ransackable_list(method)
100
+ list_type = method.to_s.delete_prefix("ransackable_")
101
+
102
+ if explicitly_defined?(method)
103
+ warn_deprecated <<~ERROR
104
+ Ransack's builtin `#{method}` method is deprecated and will result
105
+ in an error in the future. If you want to authorize the full list
106
+ of searchable #{list_type} for this model, use
107
+ `authorizable_#{method}` instead of delegating to `super`.
108
+ ERROR
109
+
110
+ public_send("authorizable_#{method}")
111
+ else
112
+ raise <<~MESSAGE
113
+ Ransack needs #{name} #{list_type} explicitly allowlisted as
114
+ searchable. Define a `#{method}` class method in your `#{name}`
115
+ model, watching out for items you DON'T want searchable (for
116
+ example, `encrypted_password`, `password_reset_token`, `owner` or
117
+ other sensitive information). You can use the following as a base:
118
+
119
+ ```ruby
120
+ class #{name} < ApplicationRecord
121
+
122
+ # ...
123
+
124
+ def self.#{method}(auth_object = nil)
125
+ #{public_send("authorizable_#{method}").sort.inspect}
126
+ end
127
+
128
+ # ...
129
+
130
+ end
131
+ ```
132
+ MESSAGE
133
+ end
134
+ end
135
+
136
+ def explicitly_defined?(method)
137
+ definer_ancestor = singleton_class.ancestors.find do |ancestor|
138
+ ancestor.instance_methods(false).include?(method)
139
+ end
140
+
141
+ definer_ancestor != Ransack::Adapters::ActiveRecord::Base
142
+ end
143
+
144
+ def warn_deprecated(message)
145
+ caller_location = caller_locations.find { |location| !location.path.start_with?(File.expand_path("../..", __dir__)) }
146
+
147
+ warn "DEPRECATION WARNING: #{message.squish} (called at #{caller_location.path}:#{caller_location.lineno})"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,373 @@
1
+ require 'ransack/context'
2
+ require 'polyamorous/polyamorous'
3
+
4
+ module Ransack
5
+ module Adapters
6
+ module ActiveRecord
7
+ class Context < ::Ransack::Context
8
+
9
+ def relation_for(object)
10
+ object.all
11
+ end
12
+
13
+ def type_for(attr)
14
+ return nil unless attr && attr.valid?
15
+ relation = attr.arel_attribute.relation
16
+ name = attr.arel_attribute.name.to_s
17
+ table = relation.respond_to?(:table_name) ? relation.table_name : relation.name
18
+ schema_cache = self.klass.connection.schema_cache
19
+ unless schema_cache.send(:data_source_exists?, table)
20
+ raise "No table named #{table} exists."
21
+ end
22
+ column = attr.klass.columns.find { |column| column.name == name }
23
+ column&.type
24
+ end
25
+
26
+ def evaluate(search, opts = {})
27
+ viz = Visitor.new
28
+ relation = @object.where(viz.accept(search.base))
29
+
30
+ if search.sorts.any?
31
+ relation = relation.except(:order)
32
+ # Rather than applying all of the search's sorts in one fell swoop,
33
+ # as the original implementation does, we apply one at a time.
34
+ #
35
+ # If the sort (returned by the Visitor above) is a symbol, we know
36
+ # that it represents a scope on the model and we can apply that
37
+ # scope.
38
+ #
39
+ # Otherwise, we fall back to the applying the sort with the "order"
40
+ # method as the original implementation did. Actually the original
41
+ # implementation used "reorder," which was overkill since we already
42
+ # have a clean slate after "relation.except(:order)" above.
43
+ viz.accept(search.sorts).each do |scope_or_sort|
44
+ if scope_or_sort.is_a?(Symbol)
45
+ relation = relation.send(scope_or_sort)
46
+ else
47
+ case Ransack.options[:postgres_fields_sort_option]
48
+ when :nulls_first
49
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
50
+ when :nulls_last
51
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
52
+ when :nulls_always_first
53
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
54
+ when :nulls_always_last
55
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
56
+ end
57
+
58
+ relation = relation.order(scope_or_sort)
59
+ end
60
+ end
61
+ end
62
+
63
+ opts[:distinct] ? relation.distinct : relation
64
+ end
65
+
66
+ def attribute_method?(str, klass = @klass)
67
+ exists = false
68
+ if ransackable_attribute?(str, klass)
69
+ exists = true
70
+ elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
71
+ remainder = []
72
+ found_assoc = nil
73
+ while !found_assoc && remainder.unshift(segments.pop) &&
74
+ segments.size > 0 do
75
+ assoc, poly_class = unpolymorphize_association(
76
+ segments.join(Constants::UNDERSCORE)
77
+ )
78
+ if found_assoc = get_association(assoc, klass)
79
+ exists = attribute_method?(
80
+ remainder.join(Constants::UNDERSCORE),
81
+ poly_class || found_assoc.klass
82
+ )
83
+ end
84
+ end
85
+ end
86
+ exists
87
+ end
88
+
89
+ def table_for(parent)
90
+ parent.table
91
+ end
92
+
93
+ def klassify(obj)
94
+ if Class === obj && ::ActiveRecord::Base > obj
95
+ obj
96
+ elsif obj.respond_to? :klass
97
+ obj.klass
98
+ elsif obj.respond_to? :base_klass
99
+ obj.base_klass
100
+ else
101
+ raise ArgumentError, "Don't know how to klassify #{obj}"
102
+ end
103
+ end
104
+
105
+ # All dependent Arel::Join nodes used in the search query.
106
+ #
107
+ # This could otherwise be done as `@object.arel.join_sources`, except
108
+ # that ActiveRecord's build_joins sets up its own JoinDependency.
109
+ # This extracts what we need to access the joins using our existing
110
+ # JoinDependency to track table aliases.
111
+ #
112
+ def join_sources
113
+ base, joins = begin
114
+ alias_tracker = @object.alias_tracker
115
+ constraints = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
116
+
117
+ [
118
+ Arel::SelectManager.new(@object.table),
119
+ constraints
120
+ ]
121
+ end
122
+ joins.each do |aliased_join|
123
+ base.from(aliased_join)
124
+ end
125
+ base.join_sources
126
+ end
127
+
128
+ def alias_tracker
129
+ @join_dependency.send(:alias_tracker)
130
+ end
131
+
132
+ def lock_association(association)
133
+ @lock_associations << association
134
+ end
135
+
136
+ def remove_association(association)
137
+ return if @lock_associations.include?(association)
138
+ @join_dependency.instance_variable_get(:@join_root).children.delete_if { |stashed|
139
+ stashed.eql?(association)
140
+ }
141
+ @object.joins_values.delete_if { |jd|
142
+ jd.instance_variables.include?(:@join_root) &&
143
+ jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
144
+ }
145
+ end
146
+
147
+ # Build an Arel subquery that selects keys for the top query,
148
+ # drawn from the first join association's foreign_key.
149
+ #
150
+ # Example: for an Article that has_and_belongs_to_many Tags
151
+ #
152
+ # context = Article.search.context
153
+ # attribute = Attribute.new(context, "tags_name").tap do |a|
154
+ # context.bind(a, a.name)
155
+ # end
156
+ # context.build_correlated_subquery(attribute.parent).to_sql
157
+ #
158
+ # # SELECT "articles_tags"."article_id" FROM "articles_tags"
159
+ # # INNER JOIN "tags" ON "tags"."id" = "articles_tags"."tag_id"
160
+ # # WHERE "articles_tags"."article_id" = "articles"."id"
161
+ #
162
+ # The WHERE condition on this query makes it invalid by itself,
163
+ # because it is correlated to the primary key on the outer query.
164
+ #
165
+ def build_correlated_subquery(association)
166
+ join_constraints = extract_joins(association)
167
+ join_root = join_constraints.shift
168
+ correlated_key = extract_correlated_key(join_root)
169
+ subquery = Arel::SelectManager.new(association.base_klass)
170
+ subquery.from(join_root.left)
171
+ subquery.project(correlated_key)
172
+ join_constraints.each do |j|
173
+ subquery.join_sources << Arel::Nodes::InnerJoin.new(j.left, j.right)
174
+ end
175
+
176
+ # Handle polymorphic associations where correlated_key is an array
177
+ if correlated_key.is_a?(Array)
178
+ # For polymorphic associations, we need to add conditions for both the foreign key and type
179
+ correlated_key.each_with_index do |key, index|
180
+ if index == 0
181
+ # This is the foreign key
182
+ subquery = subquery.where(key.eq(primary_key))
183
+ else
184
+ # This is the type key, which should be equal to the model name
185
+ subquery = subquery.where(key.eq(@klass.name))
186
+ end
187
+ end
188
+ else
189
+ # Original behavior for non-polymorphic associations
190
+ subquery = subquery.where(correlated_key.eq(primary_key))
191
+ end
192
+
193
+ subquery
194
+ end
195
+
196
+ def primary_key
197
+ @object.table[@object.primary_key]
198
+ end
199
+
200
+ private
201
+
202
+ def extract_correlated_key(join_root)
203
+ case join_root
204
+ when Arel::Nodes::OuterJoin
205
+ # one of join_root.right/join_root.left is expected to be Arel::Nodes::On
206
+ if join_root.right.is_a?(Arel::Nodes::On)
207
+ extract_correlated_key(join_root.right.expr)
208
+ elsif join_root.left.is_a?(Arel::Nodes::On)
209
+ extract_correlated_key(join_root.left.expr)
210
+ else
211
+ raise 'Ransack encountered an unexpected arel structure'
212
+ end
213
+ when Arel::Nodes::Equality
214
+ pk = primary_key
215
+ if join_root.left == pk
216
+ join_root.right
217
+ elsif join_root.right == pk
218
+ join_root.left
219
+ else
220
+ nil
221
+ end
222
+ when Arel::Nodes::And
223
+ # And may have multiple children, so we need to check all, not via left/right
224
+ if join_root.children.any?
225
+ join_root.children.each do |child|
226
+ key = extract_correlated_key(child)
227
+ return key if key
228
+ end
229
+ else
230
+ extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
231
+ end
232
+ else
233
+ # eg parent was Arel::Nodes::And and the evaluated side was one of
234
+ # Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
235
+ nil
236
+ end
237
+ end
238
+
239
+ def get_parent_and_attribute_name(str, parent = @base)
240
+ attr_name = nil
241
+
242
+ if ransackable_attribute?(str, klassify(parent))
243
+ attr_name = str
244
+ elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
245
+ remainder = []
246
+ found_assoc = nil
247
+ while remainder.unshift(segments.pop) && segments.size > 0 &&
248
+ !found_assoc do
249
+ assoc, klass = unpolymorphize_association(
250
+ segments.join(Constants::UNDERSCORE)
251
+ )
252
+ if found_assoc = get_association(assoc, parent)
253
+ join = build_or_find_association(
254
+ found_assoc.name, parent, klass
255
+ )
256
+ parent, attr_name = get_parent_and_attribute_name(
257
+ remainder.join(Constants::UNDERSCORE), join
258
+ )
259
+ end
260
+ end
261
+ end
262
+
263
+ [parent, attr_name]
264
+ end
265
+
266
+ def get_association(str, parent = @base)
267
+ klass = klassify parent
268
+ ransackable_association?(str, klass) &&
269
+ klass.reflect_on_all_associations.detect { |a| a.name.to_s == str }
270
+ end
271
+
272
+ def join_dependency(relation)
273
+ if relation.respond_to?(:join_dependency) # Polyamorous enables this
274
+ relation.join_dependency
275
+ else
276
+ build_joins(relation)
277
+ end
278
+ end
279
+
280
+ # Checkout active_record/relation/query_methods.rb +build_joins+ for
281
+ # reference. Lots of duplicated code maybe we can avoid it
282
+ def build_joins(relation)
283
+ buckets = relation.joins_values + relation.left_outer_joins_values
284
+
285
+ buckets = buckets.group_by do |join|
286
+ case join
287
+ when String
288
+ :string_join
289
+ when Hash, Symbol, Array
290
+ :association_join
291
+ when Polyamorous::JoinDependency, Polyamorous::JoinAssociation
292
+ :stashed_join
293
+ when Arel::Nodes::Join
294
+ :join_node
295
+ else
296
+ raise 'unknown class: %s' % join.class.name
297
+ end
298
+ end
299
+ buckets.default = []
300
+ association_joins = buckets[:association_join]
301
+ stashed_association_joins = buckets[:stashed_join]
302
+ join_nodes = buckets[:join_node].uniq
303
+ string_joins = buckets[:string_join].map(&:strip)
304
+ string_joins.uniq!
305
+
306
+ join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
307
+
308
+ alias_tracker = relation.alias_tracker(join_list)
309
+ join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
310
+ join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
311
+ join_nodes.each do |join|
312
+ join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
313
+ end
314
+ join_dependency
315
+ end
316
+
317
+ def convert_join_strings_to_ast(table, joins)
318
+ joins.map! { |join| table.create_string_join(Arel.sql(join)) unless join.blank? }
319
+ joins.compact!
320
+ joins
321
+ end
322
+
323
+ def build_or_find_association(name, parent = @base, klass = nil)
324
+ find_association(name, parent, klass) or build_association(name, parent, klass)
325
+ end
326
+
327
+ def find_association(name, parent = @base, klass = nil)
328
+ @join_dependency.instance_variable_get(:@join_root).children.detect do |assoc|
329
+ assoc.reflection.name == name && assoc.table &&
330
+ (@associations_pot.empty? || @associations_pot[assoc] == parent || !@associations_pot.key?(assoc)) &&
331
+ (!klass || assoc.reflection.klass == klass)
332
+ end
333
+ end
334
+
335
+ def build_association(name, parent = @base, klass = nil)
336
+ jd = Polyamorous::JoinDependency.new(
337
+ parent.base_klass,
338
+ parent.table,
339
+ Polyamorous::Join.new(name, @join_type, klass),
340
+ @join_type
341
+ )
342
+ found_association = jd.instance_variable_get(:@join_root).children.last
343
+
344
+ @associations_pot[found_association] = parent
345
+
346
+ # TODO maybe we dont need to push associations here, we could loop
347
+ # through the @associations_pot instead
348
+ @join_dependency.instance_variable_get(:@join_root).children.push found_association
349
+
350
+ # Builds the arel nodes properly for this association
351
+ @tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
352
+
353
+ # Leverage the stashed association functionality in AR
354
+ @object = @object.joins(jd)
355
+ found_association
356
+ end
357
+
358
+ def extract_joins(association)
359
+ parent = @join_dependency.instance_variable_get(:@join_root)
360
+ reflection = association.reflection
361
+ join_constraints = association.join_constraints_with_tables(
362
+ parent.table,
363
+ parent.base_klass,
364
+ Arel::Nodes::OuterJoin,
365
+ @join_dependency.instance_variable_get(:@alias_tracker),
366
+ @tables_pot[association]
367
+ )
368
+ join_constraints.to_a.flatten
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end