ransack 1.7.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/test.yml +120 -0
  5. data/.gitignore +3 -0
  6. data/CHANGELOG.md +463 -27
  7. data/CONTRIBUTING.md +52 -22
  8. data/Gemfile +24 -24
  9. data/README.md +453 -126
  10. data/Rakefile +6 -25
  11. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +24 -0
  12. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +79 -0
  13. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  14. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
  15. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
  16. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
  17. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  18. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  19. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
  20. data/lib/polyamorous/join.rb +70 -0
  21. data/lib/polyamorous/polyamorous.rb +24 -0
  22. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  23. data/lib/polyamorous/tree_node.rb +7 -0
  24. data/lib/ransack/adapters/active_record/base.rb +27 -2
  25. data/lib/ransack/adapters/active_record/context.rb +213 -139
  26. data/lib/ransack/adapters/active_record/ransack/constants.rb +70 -55
  27. data/lib/ransack/adapters/active_record/ransack/context.rb +10 -18
  28. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +42 -32
  29. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  30. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  31. data/lib/ransack/adapters/active_record.rb +11 -10
  32. data/lib/ransack/adapters.rb +45 -23
  33. data/lib/ransack/configuration.rb +107 -4
  34. data/lib/ransack/constants.rb +13 -26
  35. data/lib/ransack/context.rb +45 -33
  36. data/lib/ransack/helpers/form_builder.rb +21 -12
  37. data/lib/ransack/helpers/form_helper.rb +75 -70
  38. data/lib/ransack/locale/ar.yml +70 -0
  39. data/lib/ransack/locale/az.yml +70 -0
  40. data/lib/ransack/locale/bg.yml +70 -0
  41. data/lib/ransack/locale/ca.yml +70 -0
  42. data/lib/ransack/locale/da.yml +70 -0
  43. data/lib/ransack/locale/el.yml +70 -0
  44. data/lib/ransack/locale/es.yml +22 -22
  45. data/lib/ransack/locale/fa.yml +70 -0
  46. data/lib/ransack/locale/fi.yml +71 -0
  47. data/lib/ransack/locale/id.yml +70 -0
  48. data/lib/ransack/locale/it.yml +70 -0
  49. data/lib/ransack/locale/ja.yml +70 -0
  50. data/lib/ransack/locale/nl.yml +4 -4
  51. data/lib/ransack/locale/pt-BR.yml +70 -0
  52. data/lib/ransack/locale/ru.yml +70 -0
  53. data/lib/ransack/locale/sk.yml +70 -0
  54. data/lib/ransack/locale/tr.yml +70 -0
  55. data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
  56. data/lib/ransack/locale/zh-TW.yml +70 -0
  57. data/lib/ransack/nodes/attribute.rb +5 -2
  58. data/lib/ransack/nodes/bindable.rb +18 -6
  59. data/lib/ransack/nodes/condition.rb +85 -28
  60. data/lib/ransack/nodes/grouping.rb +17 -11
  61. data/lib/ransack/nodes/sort.rb +9 -5
  62. data/lib/ransack/nodes/value.rb +74 -68
  63. data/lib/ransack/nodes.rb +1 -1
  64. data/lib/ransack/predicate.rb +17 -20
  65. data/lib/ransack/search.rb +17 -8
  66. data/lib/ransack/translate.rb +115 -115
  67. data/lib/ransack/version.rb +1 -1
  68. data/lib/ransack/visitor.rb +1 -12
  69. data/lib/ransack.rb +9 -9
  70. data/logo/ransack-h.png +0 -0
  71. data/logo/ransack-h.svg +34 -0
  72. data/logo/ransack-v.png +0 -0
  73. data/logo/ransack-v.svg +34 -0
  74. data/logo/ransack.png +0 -0
  75. data/logo/ransack.svg +21 -0
  76. data/ransack.gemspec +7 -24
  77. data/spec/console.rb +4 -0
  78. data/spec/helpers/polyamorous_helper.rb +19 -0
  79. data/spec/polyamorous/join_association_spec.rb +35 -0
  80. data/spec/polyamorous/join_dependency_spec.rb +97 -0
  81. data/spec/polyamorous/join_spec.rb +19 -0
  82. data/spec/ransack/adapters/active_record/base_spec.rb +370 -75
  83. data/spec/ransack/adapters/active_record/context_spec.rb +72 -34
  84. data/spec/ransack/configuration_spec.rb +97 -14
  85. data/spec/ransack/helpers/form_builder_spec.rb +2 -11
  86. data/spec/ransack/helpers/form_helper_spec.rb +481 -113
  87. data/spec/ransack/nodes/condition_spec.rb +24 -0
  88. data/spec/ransack/nodes/grouping_spec.rb +56 -0
  89. data/spec/ransack/predicate_spec.rb +79 -5
  90. data/spec/ransack/search_spec.rb +207 -81
  91. data/spec/spec_helper.rb +8 -0
  92. data/spec/support/schema.rb +100 -42
  93. metadata +57 -184
  94. data/.travis.yml +0 -69
  95. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  96. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
  97. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
  98. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  99. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  100. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  101. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  102. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  103. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  104. data/lib/ransack/adapters/mongoid/base.rb +0 -130
  105. data/lib/ransack/adapters/mongoid/context.rb +0 -208
  106. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  107. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  108. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  109. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  110. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  111. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  112. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  113. data/lib/ransack/adapters/mongoid.rb +0 -13
  114. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
  115. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  116. data/spec/mongoid/configuration_spec.rb +0 -102
  117. data/spec/mongoid/dependencies_spec.rb +0 -8
  118. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  119. data/spec/mongoid/nodes/condition_spec.rb +0 -34
  120. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  121. data/spec/mongoid/predicate_spec.rb +0 -155
  122. data/spec/mongoid/search_spec.rb +0 -446
  123. data/spec/mongoid/support/mongoid.yml +0 -6
  124. data/spec/mongoid/support/schema.rb +0 -128
  125. data/spec/mongoid/translate_spec.rb +0 -14
  126. data/spec/mongoid_spec_helper.rb +0 -59
  127. data/spec/ransack/dependencies_spec.rb +0 -12
@@ -0,0 +1,70 @@
1
+ sk:
2
+ ransack:
3
+ search: "vyhľadávanie"
4
+ predicate: "predikát"
5
+ and: "a"
6
+ or: "alebo"
7
+ any: "akýkoľvek"
8
+ all: "každý"
9
+ combinator: "kombinátor"
10
+ attribute: "atribút"
11
+ value: "hodnota"
12
+ condition: "podmienka"
13
+ sort: "poradie"
14
+ asc: "vzostupne"
15
+ desc: "zostupne"
16
+ predicates:
17
+ eq: "sa rovná"
18
+ eq_any: "sa rovná akémukoľvek"
19
+ eq_all: "sa rovná všetkým"
20
+ not_eq: "sa nerovná"
21
+ not_eq_any: "sa nerovná akémukoľvek"
22
+ not_eq_all: "sa nerovná všetkým"
23
+ matches: "zodpovedá"
24
+ matches_any: "zodpovedá akémukoľvek"
25
+ matches_all: "zodpovedá všetkým"
26
+ does_not_match: "nezodpovedá"
27
+ does_not_match_any: "nezodpovedá akémukoľvek"
28
+ does_not_match_all: "nezodpovedá všetkým"
29
+ lt: "menší ako"
30
+ lt_any: "menší ako akýkoľvek"
31
+ lt_all: "menší ako všetky"
32
+ lteq: "menší alebo rovný"
33
+ lteq_any: "menší alebo rovný akémukoľvek"
34
+ lteq_all: "menší alebo rovný všetkým"
35
+ gt: "väčší ako"
36
+ gt_any: "väčší ako akýkoľvek"
37
+ gt_all: "väčší ako všetky"
38
+ gteq: "väčší alebo rovný"
39
+ gteq_any: "väčší alebo rovný akémukoľvek"
40
+ gteq_all: "väčší alebo rovný všetkým"
41
+ in: "v"
42
+ in_any: "v akejkoľvek"
43
+ in_all: "vo všetkých"
44
+ not_in: "nie je v"
45
+ not_in_any: "nie je v akejkoľvek"
46
+ not_in_all: "nie je vo všetkých"
47
+ cont: "obsahuje"
48
+ cont_any: "obsahuje akúkoľvek"
49
+ cont_all: "obsahuje všetky"
50
+ not_cont: "neobsahuje"
51
+ not_cont_any: "neobsahuje akúkoľvek"
52
+ not_cont_all: "neobsahuje všetky"
53
+ start: "začína na"
54
+ start_any: "začína s akoukoľvek"
55
+ start_all: "začína so všetkými"
56
+ not_start: "nezačíná s"
57
+ not_start_any: "nezačíná s akoukoľvek"
58
+ not_start_all: "nezačíná so všetkými"
59
+ end: "končí s"
60
+ end_any: "končí s akoukoľvek"
61
+ end_all: "končí so všetkými"
62
+ not_end: "nekončí s"
63
+ not_end_any: "nekončí s akoukoľvek"
64
+ not_end_all: "nekončí so všetkými"
65
+ 'true': "je pravdivé"
66
+ 'false': "nie je pravdivé"
67
+ present: "je vyplnené"
68
+ blank: "je prázdne"
69
+ 'null': "je null"
70
+ not_null: "nie je null"
@@ -0,0 +1,70 @@
1
+ tr:
2
+ ransack:
3
+ search: "ara"
4
+ predicate: "doğrula"
5
+ and: "ve"
6
+ or: "veya"
7
+ any: "herhangi"
8
+ all: "hepsi"
9
+ combinator: "birleştirici"
10
+ attribute: "nitelik"
11
+ value: "değer"
12
+ condition: "şart"
13
+ sort: "sırala"
14
+ asc: "artan"
15
+ desc: "azalan"
16
+ predicates:
17
+ eq: "eşit"
18
+ eq_any: "herhangi birine eşit"
19
+ eq_all: "hepsine eşit"
20
+ not_eq: "eşit değil"
21
+ not_eq_any: "herhangi birine eşit değil"
22
+ not_eq_all: "hiçbirine eşit değil"
23
+ matches: "eşleşen"
24
+ matches_any: "herhangi biri ile eşleşen"
25
+ matches_all: "hepsi ile eşleşen"
26
+ does_not_match: "eşleşmeyen"
27
+ does_not_match_any: "herhangi biri ile eşleşmeyen"
28
+ does_not_match_all: "hiçbiri ile eşleşmeyen"
29
+ lt: "daha küçük"
30
+ lt_any: "herhangi birinden küçük"
31
+ lt_all: "hepsinden küçük"
32
+ lteq: "daha küçük veya eşit"
33
+ lteq_any: "daha küçük veya herhangi birine eşit"
34
+ lteq_all: "daha küçük veya hepsine eşit"
35
+ gt: "daha büyük "
36
+ gt_any: "herhangi birinden daha büyük"
37
+ gt_all: "hepsinden daha büyük"
38
+ gteq: "daha büyük veya eşit"
39
+ gteq_any: "daha büyük veya herhangi birine eşit"
40
+ gteq_all: "daha büyük veya hepsine eşit"
41
+ in: "içinde"
42
+ in_any: "herhangi birinde"
43
+ in_all: "hepsinde"
44
+ not_in: "içinde değil"
45
+ not_in_any: "herhangi birinde değil"
46
+ not_in_all: "hiçbirinde değil"
47
+ cont: "içeren"
48
+ cont_any: "herhangi birini içeren"
49
+ cont_all: "hepsini içeren"
50
+ not_cont: "içermeyen"
51
+ not_cont_any: "herhangi birini içermeyen"
52
+ not_cont_all: "hiçbirini birini içermeyen"
53
+ start: "ile başlayan"
54
+ start_any: "herhangi biriyle başlayan"
55
+ start_all: "hepsiyle başlayan"
56
+ not_start: "ile başlamayan"
57
+ not_start_any: "herhangi biriyle başlamayan"
58
+ not_start_all: "hiçbiriyle başlamayan"
59
+ end: "ile biten"
60
+ end_any: "herhangi biriyle biten"
61
+ end_all: "hepsi ile biten"
62
+ not_end: "ile bitmeyen"
63
+ not_end_any: "herhangi biriyle bitmeyen"
64
+ not_end_all: "hiçbiriyle bitmeyen"
65
+ 'true': "doğru"
66
+ 'false': "yanlış"
67
+ present: "mevcut"
68
+ blank: "boş"
69
+ 'null': "geçersiz"
70
+ not_null: "geçerli"
@@ -1,4 +1,4 @@
1
- zh:
1
+ zh-CN:
2
2
  ransack:
3
3
  search: "搜索"
4
4
  predicate: "基于(predicate)"
@@ -50,18 +50,18 @@ zh:
50
50
  not_cont: "不包含"
51
51
  not_cont_any: "不包含任意一个值"
52
52
  not_cont_all: "不包含所有值"
53
- start: "以改值开始"
54
- start_any: "以任意一个值开始"
55
- start_all: "以所有值开始"
56
- not_start: "不以改值开始"
57
- not_start_any: "不以任意一个值开始"
58
- not_start_all: "不以所有值开始"
59
- end: "以改值结尾"
60
- end_any: "以任意一个值结尾"
61
- end_all: "以所有值结尾"
62
- not_end: "不以改值结尾"
63
- not_end_any: "不以任意一个值结尾"
64
- not_end_all: "不以所有值结尾"
53
+ start: "始于"
54
+ start_any: "始于任一值"
55
+ start_all: "始于任意值"
56
+ not_start: "非始于"
57
+ not_start_any: "非始于任一值"
58
+ not_start_all: "非始于任意值"
59
+ end: "止于"
60
+ end_any: "止于任一值"
61
+ end_all: "止于任意值"
62
+ not_end: "非止于"
63
+ not_end_any: "非止于任一值"
64
+ not_end_all: "非止于任意值"
65
65
  'true': "等于true"
66
66
  'false': "等于false"
67
67
  present: "有值"
@@ -0,0 +1,70 @@
1
+ zh-TW:
2
+ ransack:
3
+ search: "搜尋"
4
+ predicate: "基於"
5
+ and: "而且"
6
+ or: "或者"
7
+ any: "任何"
8
+ all: "所有"
9
+ combinator: "條件組合"
10
+ attribute: "屬性"
11
+ value: "數值"
12
+ condition: "條件"
13
+ sort: "排序"
14
+ asc: "升冪排序"
15
+ desc: "降冪排序"
16
+ predicates:
17
+ eq: "等於"
18
+ eq_any: "等於任何一個值"
19
+ eq_all: "等於所有值"
20
+ not_eq: "不等於"
21
+ not_eq_any: "不等於任何一個值"
22
+ not_eq_all: "不等於所有值"
23
+ matches: "符合"
24
+ matches_any: "符合任何一個條件"
25
+ matches_all: "符合所有條件"
26
+ does_not_match: "不符合"
27
+ does_not_match_any: "不符合任何一個條件"
28
+ does_not_match_all: "不符合所有條件"
29
+ lt: "小於"
30
+ lt_any: "小於任何一個值"
31
+ lt_all: "小於所有值"
32
+ lteq: "小於或等於"
33
+ lteq_any: "小於或等於任何一個值"
34
+ lteq_all: "小於或等於所有值"
35
+ gt: "大於"
36
+ gt_any: "大於任何一個值"
37
+ gt_all: "大於所有值"
38
+ gteq: "大於或等於"
39
+ gteq_any: "大於或等於任何一個值"
40
+ gteq_all: "大於或等於所有值"
41
+ in: "被包含於"
42
+ in_any: "被包含於任何一個值"
43
+ in_all: "被包含於所有值"
44
+ not_in: "不被包含於"
45
+ not_in_any: "不被包含於任何一個值"
46
+ not_in_all: "不被包含於所有值"
47
+ cont: "包含"
48
+ cont_any: "包含任何一個值"
49
+ cont_all: "包含所有值"
50
+ not_cont: "不包含"
51
+ not_cont_any: "不包含任何一個值"
52
+ not_cont_all: "不包含所有值"
53
+ start: "以某個值開始"
54
+ start_any: "以任何一個值開始"
55
+ start_all: "以所有值開始"
56
+ not_start: "不以某個值開始"
57
+ not_start_any: "不以任何一值開始"
58
+ not_start_all: "不以所有值開始"
59
+ end: "以某個值結尾"
60
+ end_any: "以任何一個值結尾"
61
+ end_all: "以所有值結尾"
62
+ not_end: "不以某個值結尾"
63
+ not_end_any: "不以任何一個值結尾"
64
+ not_end_all: "不以所有值結尾"
65
+ 'true': "為真"
66
+ 'false': "為假"
67
+ present: "有值"
68
+ blank: "為空"
69
+ 'null': "為 null"
70
+ not_null: "不為 null"
@@ -5,7 +5,7 @@ module Ransack
5
5
 
6
6
  attr_reader :name, :ransacker_args
7
7
 
8
- delegate :blank?, :present?, :==, :to => :name
8
+ delegate :blank?, :present?, :to => :name
9
9
  delegate :engine, :to => :context
10
10
 
11
11
  def initialize(context, name = nil, ransacker_args = [])
@@ -16,7 +16,6 @@ module Ransack
16
16
 
17
17
  def name=(name)
18
18
  @name = name
19
- context.bind(self, name) unless name.blank?
20
19
  end
21
20
 
22
21
  def valid?
@@ -25,6 +24,10 @@ module Ransack
25
24
  .include?(attr_name.split('.').last)
26
25
  end
27
26
 
27
+ def associated_collection?
28
+ parent.respond_to?(:reflection) && parent.reflection.collection?
29
+ end
30
+
28
31
  def type
29
32
  if ransacker
30
33
  return ransacker.type
@@ -27,14 +27,26 @@ module Ransack
27
27
 
28
28
  private
29
29
 
30
- def get_arel_attribute
31
- if ransacker
32
- ransacker.attr_from(self)
33
- else
34
- context.table_for(parent)[attr_name]
35
- end
30
+ def get_arel_attribute
31
+ if ransacker
32
+ ransacker.attr_from(self)
33
+ else
34
+ get_attribute
36
35
  end
36
+ end
37
37
 
38
+ def get_attribute
39
+ if is_alias_attribute?
40
+ context.table_for(parent)[parent.base_klass.attribute_aliases[attr_name]]
41
+ else
42
+ context.table_for(parent)[attr_name]
43
+ end
44
+ end
45
+
46
+ def is_alias_attribute?
47
+ Ransack::SUPPORTS_ATTRIBUTE_ALIAS &&
48
+ parent.base_klass.attribute_aliases.key?(attr_name)
49
+ end
38
50
  end
39
51
  end
40
52
  end
@@ -9,9 +9,10 @@ module Ransack
9
9
 
10
10
  class << self
11
11
  def extract(context, key, values)
12
- attributes, predicate = extract_attributes_and_predicate(key, context)
12
+ attributes, predicate, combinator =
13
+ extract_values_for_condition(key, context)
14
+
13
15
  if attributes.size > 0 && predicate
14
- combinator = key.match(/_(or|and)_/) ? $1 : nil
15
16
  condition = self.new(context)
16
17
  condition.build(
17
18
  :a => attributes,
@@ -31,20 +32,34 @@ module Ransack
31
32
 
32
33
  private
33
34
 
34
- def extract_attributes_and_predicate(key, context = nil)
35
- str = key.dup
36
- name = Predicate.detect_and_strip_from_string!(str)
37
- predicate = Predicate.named(name)
38
- unless predicate || Ransack.options[:ignore_unknown_conditions]
39
- raise ArgumentError, "No valid predicate for #{key}"
40
- end
41
- if context.present? && context.attribute_method?(str)
42
- attributes = [str]
43
- else
44
- attributes = str.split(/_and_|_or_/)
35
+ def extract_values_for_condition(key, context = nil)
36
+ str = key.dup
37
+ name = Predicate.detect_and_strip_from_string!(str)
38
+ predicate = Predicate.named(name)
39
+
40
+ unless predicate || Ransack.options[:ignore_unknown_conditions]
41
+ raise ArgumentError, "No valid predicate for #{key}"
42
+ end
43
+
44
+ if context.present?
45
+ str = context.ransackable_alias(str)
46
+ end
47
+
48
+ combinator =
49
+ if str.match(/_(or|and)_/)
50
+ $1
51
+ else
52
+ nil
53
+ end
54
+
55
+ if context.present? && context.attribute_method?(str)
56
+ attributes = [str]
57
+ else
58
+ attributes = str.split(/_and_|_or_/)
59
+ end
60
+
61
+ [attributes, predicate, combinator]
45
62
  end
46
- [attributes, predicate]
47
- end
48
63
  end
49
64
 
50
65
  def valid?
@@ -64,14 +79,12 @@ module Ransack
64
79
  def attributes=(args)
65
80
  case args
66
81
  when Array
67
- args.each do |attr|
68
- attr = Attribute.new(@context, attr)
69
- self.attributes << attr if attr.valid?
82
+ args.each do |name|
83
+ build_attribute(name)
70
84
  end
71
85
  when Hash
72
86
  args.each do |index, attrs|
73
- attr = Attribute.new(@context, attrs[:name], attrs[:ransacker_args])
74
- self.attributes << attr if attr.valid?
87
+ build_attribute(attrs[:name], attrs[:ransacker_args])
75
88
  end
76
89
  else
77
90
  raise ArgumentError,
@@ -114,9 +127,32 @@ module Ransack
114
127
  alias :m= :combinator=
115
128
  alias :m :combinator
116
129
 
117
- def build_attribute(name = nil)
118
- Attribute.new(@context, name).tap do |attribute|
119
- self.attributes << attribute
130
+
131
+ # == build_attribute
132
+ #
133
+ # This method was originally called from Nodes::Grouping#new_condition
134
+ # only, without arguments, without #valid? checking, to build a new
135
+ # grouping condition.
136
+ #
137
+ # After refactoring in 235eae3, it is now called from 2 places:
138
+ #
139
+ # 1. Nodes::Condition#attributes=, with +name+ argument passed or +name+
140
+ # and +ransacker_args+. Attributes are included only if #valid?.
141
+ #
142
+ # 2. Nodes::Grouping#new_condition without arguments. In this case, the
143
+ # #valid? conditional needs to be bypassed, otherwise nothing is
144
+ # built. The `name.nil?` conditional below currently does this.
145
+ #
146
+ # TODO: Add test coverage for this behavior and ensure that `name.nil?`
147
+ # isn't fixing issue #701 by introducing untested regressions.
148
+ #
149
+ def build_attribute(name = nil, ransacker_args = [])
150
+ Attribute.new(@context, name, ransacker_args).tap do |attribute|
151
+ @context.bind(attribute, attribute.name)
152
+ self.attributes << attribute if name.nil? || attribute.valid?
153
+ if predicate && !negative?
154
+ @context.lock_association(attribute.parent)
155
+ end
120
156
  end
121
157
  end
122
158
 
@@ -168,6 +204,10 @@ module Ransack
168
204
 
169
205
  def predicate_name=(name)
170
206
  self.predicate = Predicate.named(name)
207
+ unless negative?
208
+ attributes.each { |a| context.lock_association(a.parent) }
209
+ end
210
+ @predicate
171
211
  end
172
212
  alias :p= :predicate_name=
173
213
 
@@ -196,20 +236,33 @@ module Ransack
196
236
  val = predicate.format(val)
197
237
  val
198
238
  end
199
- predicate.wants_array ? formatted : formatted.first
239
+ if predicate.wants_array
240
+ formatted
241
+ else
242
+ formatted.first
243
+ end
200
244
  end
201
245
 
202
246
  def arel_predicate_for_attribute(attr)
203
247
  if predicate.arel_predicate === Proc
204
248
  values = casted_values_for_attribute(attr)
205
- predicate.arel_predicate.call(
206
- predicate.wants_array ? values : values.first
207
- )
249
+ unless predicate.wants_array
250
+ values = values.first
251
+ end
252
+ predicate.arel_predicate.call(values)
208
253
  else
209
254
  predicate.arel_predicate
210
255
  end
211
256
  end
212
257
 
258
+ def attr_value_for_attribute(attr)
259
+ return attr.attr if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
260
+
261
+ predicate.case_insensitive ? attr.attr.lower : attr.attr
262
+ rescue
263
+ attr.attr
264
+ end
265
+
213
266
 
214
267
  def default_type
215
268
  predicate.type || (attributes.first && attributes.first.type)
@@ -224,10 +277,14 @@ module Ransack
224
277
  ]
225
278
  .reject { |e| e[1].blank? }
226
279
  .map { |v| "#{v[0]}: #{v[1]}" }
227
- .join(Constants::COMMA_SPACE)
280
+ .join(', '.freeze)
228
281
  "Condition <#{data}>"
229
282
  end
230
283
 
284
+ def negative?
285
+ predicate.negative?
286
+ end
287
+
231
288
  private
232
289
 
233
290
  def valid_combinator?
@@ -44,16 +44,12 @@ module Ransack
44
44
  self.conditions << condition if condition.valid?
45
45
  end
46
46
  end
47
- self.conditions.uniq!
47
+ remove_duplicate_conditions!
48
48
  end
49
49
  alias :c= :conditions=
50
50
 
51
51
  def [](key)
52
- if condition = conditions.detect { |c| c.key == key.to_s }
53
- condition
54
- else
55
- nil
56
- end
52
+ conditions.detect { |c| c.key == key.to_s }
57
53
  end
58
54
 
59
55
  def []=(key, value)
@@ -68,7 +64,6 @@ module Ransack
68
64
  def respond_to?(method_id)
69
65
  super or begin
70
66
  method_name = method_id.to_s
71
- writer = method_name.sub!(/\=$/, Constants::EMPTY)
72
67
  attribute_method?(method_name) ? true : false
73
68
  end
74
69
  end
@@ -113,8 +108,8 @@ module Ransack
113
108
  alias :g= :groupings=
114
109
 
115
110
  def method_missing(method_id, *args)
116
- method_name = method_id.to_s
117
- writer = method_name.sub!(/\=$/, Constants::EMPTY)
111
+ method_name = method_id.to_s.dup
112
+ writer = method_name.sub!(/\=$/, ''.freeze)
118
113
  if attribute_method?(method_name)
119
114
  if writer
120
115
  write_attribute(method_name, *args)
@@ -169,7 +164,7 @@ module Ransack
169
164
  ]
170
165
  .reject { |e| e[1].blank? }
171
166
  .map { |v| "#{v[0]}: #{v[1]}" }
172
- .join(Constants::COMMA_SPACE)
167
+ .join(', '.freeze)
173
168
  "Grouping <#{data}>"
174
169
  end
175
170
 
@@ -191,10 +186,21 @@ module Ransack
191
186
  end
192
187
 
193
188
  def strip_predicate_and_index(str)
194
- string = str.split(/\(/).first
189
+ string = str[/(.+?)\(/, 1] || str.dup
195
190
  Predicate.detect_and_strip_from_string!(string)
196
191
  string
197
192
  end
193
+
194
+ def remove_duplicate_conditions!
195
+ # If self.conditions.uniq! is called without passing a block, then
196
+ # conditions differing only by ransacker_args within attributes are
197
+ # wrongly considered equal and are removed.
198
+ self.conditions.uniq! do |c|
199
+ c.attributes.map { |a| [a.name, a.ransacker_args] }.flatten +
200
+ [c.predicate.name] +
201
+ c.values.map { |v| v.value }
202
+ end
203
+ end
198
204
  end
199
205
  end
200
206
  end
@@ -3,7 +3,7 @@ module Ransack
3
3
  class Sort < Node
4
4
  include Bindable
5
5
 
6
- attr_reader :name, :dir
6
+ attr_reader :name, :dir, :ransacker_args
7
7
  i18n_word :asc, :desc
8
8
 
9
9
  class << self
@@ -16,7 +16,7 @@ module Ransack
16
16
 
17
17
  def build(params)
18
18
  params.with_indifferent_access.each do |key, value|
19
- if key.match(/^(name|dir)$/)
19
+ if key.match(/^(name|dir|ransacker_args)$/)
20
20
  self.send("#{key}=", value)
21
21
  end
22
22
  end
@@ -32,19 +32,23 @@ module Ransack
32
32
 
33
33
  def name=(name)
34
34
  @name = name
35
- context.bind(self, name) unless name.blank?
35
+ context.bind(self, name)
36
36
  end
37
37
 
38
38
  def dir=(dir)
39
39
  dir = dir.downcase if dir
40
40
  @dir =
41
- if Constants::ASC_DESC.include?(dir)
41
+ if dir == 'asc'.freeze || dir == 'desc'.freeze
42
42
  dir
43
43
  else
44
- Constants::ASC
44
+ 'asc'.freeze
45
45
  end
46
46
  end
47
47
 
48
+ def ransacker_args=(ransack_args)
49
+ @ransacker_args = ransack_args
50
+ end
51
+
48
52
  end
49
53
  end
50
54
  end