ransack 4.4.0 → 5.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/lib/ransack/adapters/active_record/context.rb +12 -10
  4. data/lib/ransack/configuration.rb +38 -5
  5. data/lib/ransack/constants.rb +36 -11
  6. data/lib/ransack/context.rb +36 -4
  7. data/lib/ransack/helpers/form_builder.rb +1 -17
  8. data/lib/ransack/helpers/form_helper.rb +7 -7
  9. data/lib/ransack/nodes/attribute.rb +11 -2
  10. data/lib/ransack/nodes/condition.rb +107 -31
  11. data/lib/ransack/nodes/grouping.rb +23 -5
  12. data/lib/ransack/nodes/node.rb +13 -0
  13. data/lib/ransack/nodes/sort.rb +1 -2
  14. data/lib/ransack/nodes/value.rb +3 -9
  15. data/lib/ransack/predicate.rb +24 -3
  16. data/lib/ransack/search.rb +65 -22
  17. data/lib/ransack/version.rb +1 -1
  18. data/lib/ransack.rb +1 -5
  19. metadata +9 -61
  20. data/spec/blueprints/articles.rb +0 -5
  21. data/spec/blueprints/comments.rb +0 -5
  22. data/spec/blueprints/notes.rb +0 -5
  23. data/spec/blueprints/people.rb +0 -8
  24. data/spec/blueprints/tags.rb +0 -3
  25. data/spec/console.rb +0 -25
  26. data/spec/helpers/polyamorous_helper.rb +0 -13
  27. data/spec/helpers/ransack_helper.rb +0 -9
  28. data/spec/polyamorous/activerecord_compatibility_spec.rb +0 -15
  29. data/spec/polyamorous/join_association_spec.rb +0 -29
  30. data/spec/polyamorous/join_dependency_spec.rb +0 -80
  31. data/spec/polyamorous/join_spec.rb +0 -19
  32. data/spec/ransack/adapters/active_record/base_spec.rb +0 -905
  33. data/spec/ransack/adapters/active_record/context_spec.rb +0 -219
  34. data/spec/ransack/configuration_spec.rb +0 -201
  35. data/spec/ransack/helpers/form_builder_spec.rb +0 -169
  36. data/spec/ransack/helpers/form_helper_spec.rb +0 -1075
  37. data/spec/ransack/nodes/condition_spec.rb +0 -333
  38. data/spec/ransack/nodes/grouping_spec.rb +0 -111
  39. data/spec/ransack/nodes/value_spec.rb +0 -126
  40. data/spec/ransack/predicate_spec.rb +0 -504
  41. data/spec/ransack/search_spec.rb +0 -814
  42. data/spec/ransack/translate_spec.rb +0 -16
  43. data/spec/spec_helper.rb +0 -65
  44. data/spec/support/en.yml +0 -18
  45. data/spec/support/schema.rb +0 -422
@@ -1,8 +1,12 @@
1
1
  module Ransack
2
2
  module Nodes
3
3
  class Grouping < Node
4
- attr_reader :conditions
5
- attr_accessor :combinator
4
+ attr_reader :combinator
5
+
6
+ # The writer is inherited from Node, which normalises the value. Defining
7
+ # it here as well — or aliasing before the definition is in place — would
8
+ # bind `m=` to the plain attribute writer and skip that normalisation,
9
+ # which is how `g: [{ m: 'OR' }]` silently fell back to AND.
6
10
  alias :m :combinator
7
11
  alias :m= :combinator=
8
12
 
@@ -13,7 +17,7 @@ module Ransack
13
17
 
14
18
  def initialize(context, combinator = nil)
15
19
  super(context)
16
- self.combinator = combinator.to_s if combinator
20
+ self.combinator = combinator if combinator
17
21
  end
18
22
 
19
23
  def persisted?
@@ -149,10 +153,14 @@ module Ransack
149
153
  def build(params)
150
154
  params.with_indifferent_access.each do |key, value|
151
155
  case key
152
- when /^(g|c|m)$/
156
+ when /^(g|c|m|groupings|conditions|combinator)$/
153
157
  self.send("#{key}=", value)
154
158
  else
155
- write_attribute(key.to_s, value)
159
+ if @context.ransackable_scope?(key, @context.object)
160
+ @context.chain_scope(key, @context.sanitize_scope_args(key, value))
161
+ else
162
+ write_attribute(key.to_s, value)
163
+ end
156
164
  end
157
165
  end
158
166
  self
@@ -178,6 +186,16 @@ module Ransack
178
186
  end
179
187
 
180
188
  def read_attribute(name)
189
+ # A field built from an aliased attribute writes its value under the real
190
+ # name but reads it back under the alias. Resolve the alias so the
191
+ # value survives a form round-trip (#689).
192
+ if self[name].nil?
193
+ stripped_name = name.dup
194
+ predicate = Predicate.detect_and_strip_from_string!(stripped_name)
195
+ aliased_attribute = context.ransackable_alias(stripped_name)
196
+ name = "#{aliased_attribute}_#{predicate}" unless aliased_attribute == stripped_name
197
+ end
198
+
181
199
  if self[name].respond_to?(:value)
182
200
  self[name].value
183
201
  else
@@ -29,6 +29,19 @@ module Ransack
29
29
  end
30
30
  end
31
31
 
32
+ # Every way of supplying a combinator — a top-level `combinator`/`m`,
33
+ # or an `m` inside any nested grouping or condition — ends up here, so
34
+ # this is the one place that normalises the spelling and, when the search
35
+ # is strict, rejects a value it does not recognise.
36
+ def combinator=(val)
37
+ normalised = val.to_s.downcase
38
+ @combinator = Constants::AND_OR.detect { |v| v == normalised }
39
+
40
+ if @combinator.nil? && !normalised.empty? && context&.strict_conditions?
41
+ raise InvalidSearchError, "Invalid combinator #{val}"
42
+ end
43
+ end
44
+
32
45
  end
33
46
  end
34
47
  end
@@ -26,8 +26,7 @@ module Ransack
26
26
 
27
27
  def valid?
28
28
  bound? && attr &&
29
- context.klassify(parent).ransortable_attributes(context.auth_object)
30
- .include?(attr_name)
29
+ context.ransortable_attribute?(attr_name.split('.').last, context.klassify(parent))
31
30
  end
32
31
 
33
32
  def name=(name)
@@ -24,9 +24,11 @@ module Ransack
24
24
 
25
25
  def cast(type)
26
26
  case type
27
+ when :enum
28
+ value
27
29
  when :date
28
30
  cast_to_date(value)
29
- when :datetime, :timestamp, :time, :timestamptz
31
+ when :datetime, :timestamp, :time, :timestamptz, :datetimeoffset
30
32
  cast_to_time(value)
31
33
  when :boolean
32
34
  cast_to_boolean(value)
@@ -43,14 +45,6 @@ module Ransack
43
45
  end
44
46
  end
45
47
 
46
- def cast_array
47
- if value.is_a?(Array)
48
- cast_to_date(value)
49
- else
50
- value
51
- end
52
- end
53
-
54
48
  def cast_to_date(val)
55
49
  if val.respond_to?(:to_date)
56
50
  val.to_date rescue nil
@@ -3,6 +3,17 @@ module Ransack
3
3
  attr_reader :name, :arel_predicate, :type, :formatter, :validator,
4
4
  :compound, :wants_array, :case_insensitive
5
5
 
6
+ # Consulted at search time rather than captured at predicate-definition
7
+ # time, so that `Ransack.options[:ignore_blank_values]` set in an
8
+ # initializer applies to the predicates registered before it ran.
9
+ DEFAULT_VALIDATOR = lambda do |v|
10
+ if Ransack.options[:ignore_blank_values]
11
+ v.respond_to?(:empty?) ? !v.empty? : !v.nil?
12
+ else
13
+ !v.nil?
14
+ end
15
+ end
16
+
6
17
  class << self
7
18
 
8
19
  def names
@@ -37,8 +48,7 @@ module Ransack
37
48
  @arel_predicate = opts[:arel_predicate]
38
49
  @type = opts[:type]
39
50
  @formatter = opts[:formatter]
40
- @validator = opts[:validator] ||
41
- lambda { |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil? }
51
+ @validator = opts[:validator] || DEFAULT_VALIDATOR
42
52
  @compound = opts[:compound]
43
53
  @wants_array = opts.fetch(:wants_array,
44
54
  @compound || Constants::IN_NOT_IN.include?(@arel_predicate))
@@ -64,7 +74,18 @@ module Ransack
64
74
  end
65
75
 
66
76
  def validate(vals, type = @type)
67
- vals.any? { |v| validator.call(type ? v.cast(type) : v.value) }
77
+ # An explicitly empty array is a meaningful filter (matching nothing)
78
+ # rather than an absent one, but only when blank values are not ignored.
79
+ return true if vals.empty? && wants_array &&
80
+ !Ransack.options[:ignore_blank_values]
81
+
82
+ # When blank values are meaningful, validate the value as given. Casting
83
+ # first would turn '' into nil for an integer or boolean column and the
84
+ # explicit blank would be dropped — leaving no condition at all.
85
+ vals.any? do |v|
86
+ value = type && Ransack.options[:ignore_blank_values] ? v.cast(type) : v.value
87
+ validator.call(value)
88
+ end
68
89
  end
69
90
 
70
91
  def negative?
@@ -24,14 +24,19 @@ module Ransack
24
24
  strip_whitespace = options.fetch(:strip_whitespace, Ransack.options[:strip_whitespace])
25
25
  params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
26
26
  if params.is_a? Hash
27
- params = params.dup
28
- params = params.transform_values { |v| v.is_a?(String) && strip_whitespace ? v.strip : v }
29
- params.delete_if { |k, v| [*v].all?{ |i| i.blank? && i != false } }
27
+ # deep_transform_values rebuilds every nested hash and array, which
28
+ # also gives the pruning below a private copy to edit the caller's
29
+ # params come back untouched. Values nested inside `g:` groupings and
30
+ # `c:` conditions are stripped too, not just the top level.
31
+ params = params.deep_transform_values { |v| v.is_a?(String) && strip_whitespace ? v.strip : v }
32
+ params.delete_if { |_k, v| blank_condition_value?(v) }
33
+ prune_blank_advanced_conditions!(params)
30
34
  else
31
35
  params = {}
32
36
  end
33
37
  @context = options[:context] || Context.for(object, options)
34
38
  @context.auth_object = options[:auth_object]
39
+ @context.ignore_unknown_conditions = options[:ignore_unknown_conditions]
35
40
  @base = Nodes::Grouping.new(
36
41
  @context, options[:grouping] || Constants::AND
37
42
  )
@@ -132,11 +137,7 @@ module Ransack
132
137
  private
133
138
 
134
139
  def add_scope(key, args)
135
- sanitized_args = if Ransack.options[:sanitize_scope_args] && !@context.ransackable_scope_skip_sanitize_args?(key, @context.object)
136
- sanitized_scope_args(args)
137
- else
138
- args
139
- end
140
+ sanitized_args = @context.sanitize_scope_args(key, args)
140
141
 
141
142
  if @context.scope_arity(key) == 1
142
143
  @scope_args[key] = args.is_a?(Array) ? args[0] : args
@@ -146,20 +147,6 @@ module Ransack
146
147
  @context.chain_scope(key, sanitized_args)
147
148
  end
148
149
 
149
- def sanitized_scope_args(args)
150
- if args.is_a?(Array)
151
- args = args.map(&method(:sanitized_scope_args))
152
- end
153
-
154
- if Constants::TRUE_VALUES.include? args
155
- true
156
- elsif Constants::FALSE_VALUES.include? args
157
- false
158
- else
159
- args
160
- end
161
- end
162
-
163
150
  def collapse_multiparameter_attributes!(attrs)
164
151
  attrs.keys.each do |k|
165
152
  if k.include?(Constants::LEFT_PARENTHESIS)
@@ -191,5 +178,61 @@ module Ransack
191
178
  attrs
192
179
  end
193
180
 
181
+ # True when a condition's value should be dropped before building. With
182
+ # `ignore_blank_values` on (the default) a blank value means "this form
183
+ # field was left empty"; with it off, only nil is treated that way and a
184
+ # blank value is a value to search for. `false` is always a real value, and
185
+ # an explicit nil inside an array is kept so `name_in: [nil]` still reaches
186
+ # the query. The `c:` pruning below shares this, so it follows the option.
187
+ def blank_condition_value?(value)
188
+ if Ransack.options[:ignore_blank_values]
189
+ [*value].all? { |i| i.blank? && i != false && !i.nil? }
190
+ else
191
+ value.nil? || (value.is_a?(Array) && !value.empty? && value.all?(&:nil?))
192
+ end
193
+ end
194
+
195
+ # The low-level `c:` API nests its values a level deeper than the shorthand
196
+ # form, so the filter above never sees them. Left in place, a condition with
197
+ # an empty value still builds its attribute and contributes a join, giving a
198
+ # LEFT OUTER JOIN with no WHERE clause to go with it.
199
+ def prune_blank_advanced_conditions!(node)
200
+ return unless node.is_a?(Hash)
201
+
202
+ conditions = fetch_either(node, :c, :conditions)
203
+ case conditions
204
+ when Array then conditions.delete_if { |c| blank_advanced_condition?(c) }
205
+ when Hash then conditions.delete_if { |_k, c| blank_advanced_condition?(c) }
206
+ end
207
+
208
+ # Groupings nest to any depth and carry their own conditions, so descend
209
+ # into each of them too.
210
+ groupings = fetch_either(node, :g, :groupings)
211
+ groupings = groupings.values if groupings.is_a?(Hash)
212
+ Array(groupings).each { |grouping| prune_blank_advanced_conditions!(grouping) }
213
+ end
214
+
215
+ # Params are not yet indifferent-access here, so look under both spellings
216
+ # of a key and both its short and long forms.
217
+ def fetch_either(hash, short, long)
218
+ hash[short] || hash[short.to_s] || hash[long] || hash[long.to_s]
219
+ end
220
+
221
+ def blank_advanced_condition?(condition)
222
+ return false unless condition.is_a?(Hash)
223
+
224
+ values = condition[:v] || condition['v']
225
+
226
+ case values
227
+ when Array then values.all? { |v| blank_condition_value?(unwrap_value(v)) }
228
+ when Hash then values.all? { |_k, v| blank_condition_value?(unwrap_value(v)) }
229
+ end
230
+ end
231
+
232
+ # Values in the `c:` API may be given bare or wrapped in a `{ value: ... }`
233
+ # envelope, the same two forms `Condition#values=` accepts.
234
+ def unwrap_value(value)
235
+ value.is_a?(Hash) ? (value[:value] || value['value']) : value
236
+ end
194
237
  end
195
238
  end
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '4.4.0'
2
+ VERSION = '5.0.0'
3
3
  end
data/lib/ransack.rb CHANGED
@@ -1,10 +1,6 @@
1
1
  require 'active_support/dependencies/autoload'
2
2
  require 'active_support/deprecation'
3
- require 'active_support/version'
4
-
5
- if ::ActiveSupport.version >= ::Gem::Version.new("7.1")
6
- require 'active_support/deprecator'
7
- end
3
+ require 'active_support/deprecator'
8
4
 
9
5
  require 'active_support/core_ext'
10
6
  require 'ransack/configuration'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ransack
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernie Miller
@@ -11,7 +11,7 @@ authors:
11
11
  - David Rodríguez
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2025-09-25 00:00:00.000000000 Z
14
+ date: 1980-01-02 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activerecord
@@ -19,28 +19,28 @@ dependencies:
19
19
  requirements:
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: '7.1'
22
+ version: '7.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '7.1'
29
+ version: '7.2'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: activesupport
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  requirements:
34
34
  - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: '7.1'
36
+ version: '7.2'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: '7.1'
43
+ version: '7.2'
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: i18n
46
46
  requirement: !ruby/object:Gem::Requirement
@@ -130,32 +130,6 @@ files:
130
130
  - lib/ransack/translate.rb
131
131
  - lib/ransack/version.rb
132
132
  - lib/ransack/visitor.rb
133
- - spec/blueprints/articles.rb
134
- - spec/blueprints/comments.rb
135
- - spec/blueprints/notes.rb
136
- - spec/blueprints/people.rb
137
- - spec/blueprints/tags.rb
138
- - spec/console.rb
139
- - spec/helpers/polyamorous_helper.rb
140
- - spec/helpers/ransack_helper.rb
141
- - spec/polyamorous/activerecord_compatibility_spec.rb
142
- - spec/polyamorous/join_association_spec.rb
143
- - spec/polyamorous/join_dependency_spec.rb
144
- - spec/polyamorous/join_spec.rb
145
- - spec/ransack/adapters/active_record/base_spec.rb
146
- - spec/ransack/adapters/active_record/context_spec.rb
147
- - spec/ransack/configuration_spec.rb
148
- - spec/ransack/helpers/form_builder_spec.rb
149
- - spec/ransack/helpers/form_helper_spec.rb
150
- - spec/ransack/nodes/condition_spec.rb
151
- - spec/ransack/nodes/grouping_spec.rb
152
- - spec/ransack/nodes/value_spec.rb
153
- - spec/ransack/predicate_spec.rb
154
- - spec/ransack/search_spec.rb
155
- - spec/ransack/translate_spec.rb
156
- - spec/spec_helper.rb
157
- - spec/support/en.yml
158
- - spec/support/schema.rb
159
133
  homepage: https://github.com/activerecord-hackery/ransack
160
134
  licenses:
161
135
  - MIT
@@ -168,40 +142,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
142
  requirements:
169
143
  - - ">="
170
144
  - !ruby/object:Gem::Version
171
- version: '3.0'
145
+ version: '3.1'
172
146
  required_rubygems_version: !ruby/object:Gem::Requirement
173
147
  requirements:
174
148
  - - ">="
175
149
  - !ruby/object:Gem::Version
176
150
  version: '0'
177
151
  requirements: []
178
- rubygems_version: 3.6.2
152
+ rubygems_version: 3.6.9
179
153
  specification_version: 4
180
154
  summary: Object-based searching for Active Record.
181
- test_files:
182
- - spec/blueprints/articles.rb
183
- - spec/blueprints/comments.rb
184
- - spec/blueprints/notes.rb
185
- - spec/blueprints/people.rb
186
- - spec/blueprints/tags.rb
187
- - spec/console.rb
188
- - spec/helpers/polyamorous_helper.rb
189
- - spec/helpers/ransack_helper.rb
190
- - spec/polyamorous/activerecord_compatibility_spec.rb
191
- - spec/polyamorous/join_association_spec.rb
192
- - spec/polyamorous/join_dependency_spec.rb
193
- - spec/polyamorous/join_spec.rb
194
- - spec/ransack/adapters/active_record/base_spec.rb
195
- - spec/ransack/adapters/active_record/context_spec.rb
196
- - spec/ransack/configuration_spec.rb
197
- - spec/ransack/helpers/form_builder_spec.rb
198
- - spec/ransack/helpers/form_helper_spec.rb
199
- - spec/ransack/nodes/condition_spec.rb
200
- - spec/ransack/nodes/grouping_spec.rb
201
- - spec/ransack/nodes/value_spec.rb
202
- - spec/ransack/predicate_spec.rb
203
- - spec/ransack/search_spec.rb
204
- - spec/ransack/translate_spec.rb
205
- - spec/spec_helper.rb
206
- - spec/support/en.yml
207
- - spec/support/schema.rb
155
+ test_files: []
@@ -1,5 +0,0 @@
1
- Article.blueprint do
2
- person
3
- title
4
- body
5
- end
@@ -1,5 +0,0 @@
1
- Comment.blueprint do
2
- article
3
- person
4
- body
5
- end
@@ -1,5 +0,0 @@
1
- Note.blueprint do
2
- note
3
- notable_type { "Article" }
4
- notable_id
5
- end
@@ -1,8 +0,0 @@
1
- Person.blueprint do
2
- name
3
- email { "test@example.com" }
4
- salary
5
- only_sort
6
- only_search
7
- only_admin
8
- end
@@ -1,3 +0,0 @@
1
- Tag.blueprint do
2
- name { Sham.tag_name }
3
- end
data/spec/console.rb DELETED
@@ -1,25 +0,0 @@
1
- Bundler.setup
2
- require 'machinist/active_record'
3
- require 'sham'
4
- require 'faker'
5
- require 'ransack'
6
-
7
- Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)]
8
- .each do |f|
9
- require f
10
- end
11
-
12
- Sham.define do
13
- name { Faker::Name.name }
14
- title { Faker::Lorem.sentence }
15
- body { Faker::Lorem.paragraph }
16
- salary { |index| 30000 + (index * 1000) }
17
- tag_name { Faker::Lorem.words(number: 3).join(' ') }
18
- note { Faker::Lorem.words(number: 7).join(' ') }
19
- only_admin { Faker::Lorem.words(number: 3).join(' ') }
20
- only_search { Faker::Lorem.words(number: 3).join(' ') }
21
- only_sort { Faker::Lorem.words(number: 3).join(' ') }
22
- notable_id { |id| id }
23
- end
24
-
25
- Schema.create
@@ -1,13 +0,0 @@
1
- module PolyamorousHelper
2
- def new_join_association(reflection, children, klass)
3
- Polyamorous::JoinAssociation.new reflection, children, klass
4
- end
5
-
6
- def new_join_dependency(klass, associations = {})
7
- Polyamorous::JoinDependency.new klass, klass.arel_table, associations, Polyamorous::InnerJoin
8
- end
9
-
10
- def new_join(name, type = Polyamorous::InnerJoin, klass = nil)
11
- Polyamorous::Join.new name, type, klass
12
- end
13
- end
@@ -1,9 +0,0 @@
1
- module RansackHelper
2
- def quote_table_name(table)
3
- ActiveRecord::Base.connection.quote_table_name(table)
4
- end
5
-
6
- def quote_column_name(column)
7
- ActiveRecord::Base.connection.quote_column_name(column)
8
- end
9
- end
@@ -1,15 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Polyamorous
4
- describe "ActiveRecord Compatibility" do
5
- it 'works with self joins and includes' do
6
- trade_account = Account.create!
7
- Account.create!(trade_account: trade_account)
8
-
9
- accounts = Account.joins(:trade_account).includes(:trade_account, :agent_account)
10
- account = accounts.first
11
-
12
- expect(account.agent_account).to be_nil
13
- end
14
- end
15
- end
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Polyamorous
4
- describe JoinAssociation do
5
- let(:join_dependency) { new_join_dependency Note, {} }
6
- let(:reflection) { Note.reflect_on_association(:notable) }
7
- let(:parent) { join_dependency.send(:join_root) }
8
- let(:join_association) {
9
- new_join_association(reflection, parent.children, Article)
10
- }
11
-
12
- subject { new_join_association(reflection, parent.children, Person) }
13
-
14
- it 'leaves the original reflection intact for thread safety' do
15
- reflection.instance_variable_set(:@klass, Article)
16
- join_association
17
- .swapping_reflection_klass(reflection, Person) do |new_reflection|
18
- expect(new_reflection.options).not_to equal reflection.options
19
- expect(new_reflection.options).not_to have_key(:polymorphic)
20
- expect(new_reflection.klass).to eq(Person)
21
- expect(reflection.klass).to eq(Article)
22
- end
23
- end
24
-
25
- it 'sets the polymorphic option to true after initializing' do
26
- expect(join_association.reflection.options[:polymorphic]).to be true
27
- end
28
- end
29
- end
@@ -1,80 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Polyamorous
4
- describe JoinDependency do
5
- context 'with symbol joins' do
6
- subject { new_join_dependency Person, articles: :comments }
7
-
8
- specify { expect(subject.send(:join_root).drop(1).size)
9
- .to eq(2) }
10
- specify { expect(subject.send(:join_root).drop(1).map(&:join_type).uniq)
11
- .to eq [Polyamorous::InnerJoin] }
12
- end
13
-
14
- context 'with has_many :through association' do
15
- subject { new_join_dependency Person, :authored_article_comments }
16
-
17
- specify { expect(subject.send(:join_root).drop(1).size)
18
- .to eq 1 }
19
- specify { expect(subject.send(:join_root).drop(1).first.table_name)
20
- .to eq 'comments' }
21
- end
22
-
23
- context 'with outer join' do
24
- subject { new_join_dependency Person, new_join(:articles, :outer) }
25
-
26
- specify { expect(subject.send(:join_root).drop(1).size)
27
- .to eq 1 }
28
- specify { expect(subject.send(:join_root).drop(1).first.join_type)
29
- .to eq Polyamorous::OuterJoin }
30
- end
31
-
32
- context 'with nested outer joins' do
33
- subject { new_join_dependency Person,
34
- new_join(:articles, :outer) => new_join(:comments, :outer) }
35
-
36
- specify { expect(subject.send(:join_root).drop(1).size)
37
- .to eq 2 }
38
- specify { expect(subject.send(:join_root).drop(1).map(&:join_type))
39
- .to eq [Polyamorous::OuterJoin, Polyamorous::OuterJoin] }
40
- specify { expect(subject.send(:join_root).drop(1).map(&:join_type).uniq)
41
- .to eq [Polyamorous::OuterJoin] }
42
- end
43
-
44
- context 'with polymorphic belongs_to join' do
45
- subject { new_join_dependency Note, new_join(:notable, :inner, Person) }
46
-
47
- specify { expect(subject.send(:join_root).drop(1).size)
48
- .to eq 1 }
49
- specify { expect(subject.send(:join_root).drop(1).first.join_type)
50
- .to eq Polyamorous::InnerJoin }
51
- specify { expect(subject.send(:join_root).drop(1).first.table_name)
52
- .to eq 'people' }
53
- end
54
-
55
- context 'with polymorphic belongs_to join and nested symbol join' do
56
- subject { new_join_dependency Note,
57
- new_join(:notable, :inner, Person) => :comments }
58
-
59
- specify { expect(subject.send(:join_root).drop(1).size)
60
- .to eq 2 }
61
- specify { expect(subject.send(:join_root).drop(1).map(&:join_type).uniq)
62
- .to eq [Polyamorous::InnerJoin] }
63
- specify { expect(subject.send(:join_root).drop(1).first.table_name)
64
- .to eq 'people' }
65
- specify { expect(subject.send(:join_root).drop(1)[1].table_name)
66
- .to eq 'comments' }
67
- end
68
-
69
- context 'with polymorphic belongs_to join and nested join' do
70
- subject { new_join_dependency Note,
71
- new_join(:notable, :outer, Person) => :comments }
72
- specify { expect(subject.send(:join_root).drop(1).size).to eq 2 }
73
- specify { expect(subject.send(:join_root).drop(1).map(&:join_type)).to eq [Polyamorous::OuterJoin, Polyamorous::InnerJoin] }
74
- specify { expect(subject.send(:join_root).drop(1).first.table_name)
75
- .to eq 'people' }
76
- specify { expect(subject.send(:join_root).drop(1)[1].table_name)
77
- .to eq 'comments' }
78
- end
79
- end
80
- end
@@ -1,19 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Polyamorous
4
- describe Join do
5
- it 'is a tree node' do
6
- join = new_join(:articles, :outer)
7
- expect(join).to be_kind_of(TreeNode)
8
- end
9
-
10
- it 'can be added to a tree' do
11
- join = new_join(:articles, :outer)
12
-
13
- tree_hash = {}
14
- join.add_to_tree(tree_hash)
15
-
16
- expect(tree_hash[join]).to be {}
17
- end
18
- end
19
- end