ransack 1.7.0 → 2.4.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 (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