mini-fast-sys 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/mini-fast-sys.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 +70 -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,176 @@
1
+ module Ransack
2
+ module Constants
3
+ OR = 'or'.freeze
4
+ AND = 'and'.freeze
5
+
6
+ CAP_SEARCH = 'Search'.freeze
7
+ SEARCH = 'search'.freeze
8
+ SEARCHES = 'searches'.freeze
9
+
10
+ ATTRIBUTE = 'attribute'.freeze
11
+ ATTRIBUTES = 'attributes'.freeze
12
+ COMBINATOR = 'combinator'.freeze
13
+
14
+ TWO_COLONS = '::'.freeze
15
+ UNDERSCORE = '_'.freeze
16
+ LEFT_PARENTHESIS = '('.freeze
17
+ Q = 'q'.freeze
18
+ I = 'i'.freeze
19
+ DOT_ASTERIX = '.*'.freeze
20
+
21
+ STRING_JOIN = 'string_join'.freeze
22
+ ASSOCIATION_JOIN = 'association_join'.freeze
23
+ STASHED_JOIN = 'stashed_join'.freeze
24
+ JOIN_NODE = 'join_node'.freeze
25
+
26
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
27
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
28
+ BOOLEAN_VALUES = (TRUE_VALUES + FALSE_VALUES).freeze
29
+
30
+ AND_OR = ['and'.freeze, 'or'.freeze].freeze
31
+ IN_NOT_IN = ['in'.freeze, 'not_in'.freeze].freeze
32
+ SUFFIXES = ['_any'.freeze, '_all'.freeze].freeze
33
+ AREL_PREDICATES = [
34
+ 'eq'.freeze, 'not_eq'.freeze,
35
+ 'matches'.freeze, 'does_not_match'.freeze,
36
+ 'lt'.freeze, 'lteq'.freeze,
37
+ 'gt'.freeze, 'gteq'.freeze,
38
+ 'in'.freeze, 'not_in'.freeze
39
+ ].freeze
40
+ A_S_I = ['a'.freeze, 's'.freeze, 'i'.freeze].freeze
41
+
42
+ EQ = 'eq'.freeze
43
+ NOT_EQ = 'not_eq'.freeze
44
+ EQ_ANY = 'eq_any'.freeze
45
+ NOT_EQ_ALL = 'not_eq_all'.freeze
46
+ CONT = 'cont'.freeze
47
+
48
+ RANSACK_SLASH_SEARCHES = 'ransack/searches'.freeze
49
+ RANSACK_SLASH_SEARCHES_SLASH_SEARCH = 'ransack/searches/search'.freeze
50
+
51
+ DISTINCT = 'DISTINCT '.freeze
52
+
53
+ DERIVED_PREDICATES = [
54
+ [CONT, {
55
+ arel_predicate: 'matches'.freeze,
56
+ formatter: proc { |v| "%#{escape_wildcards(v)}%" }
57
+ }
58
+ ],
59
+ ['not_cont'.freeze, {
60
+ arel_predicate: 'does_not_match'.freeze,
61
+ formatter: proc { |v| "%#{escape_wildcards(v)}%" }
62
+ }
63
+ ],
64
+ ['i_cont'.freeze, {
65
+ arel_predicate: 'matches'.freeze,
66
+ formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
67
+ case_insensitive: true
68
+ }
69
+ ],
70
+ ['not_i_cont'.freeze, {
71
+ arel_predicate: 'does_not_match'.freeze,
72
+ formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
73
+ case_insensitive: true
74
+ }
75
+ ],
76
+ ['start'.freeze, {
77
+ arel_predicate: 'matches'.freeze,
78
+ formatter: proc { |v| "#{escape_wildcards(v)}%" }
79
+ }
80
+ ],
81
+ ['not_start'.freeze, {
82
+ arel_predicate: 'does_not_match'.freeze,
83
+ formatter: proc { |v| "#{escape_wildcards(v)}%" }
84
+ }
85
+ ],
86
+ ['end'.freeze, {
87
+ arel_predicate: 'matches'.freeze,
88
+ formatter: proc { |v| "%#{escape_wildcards(v)}" }
89
+ }
90
+ ],
91
+ ['not_end'.freeze, {
92
+ arel_predicate: 'does_not_match'.freeze,
93
+ formatter: proc { |v| "%#{escape_wildcards(v)}" }
94
+ }
95
+ ],
96
+ ['true'.freeze, {
97
+ arel_predicate: proc { |v| v ? EQ : NOT_EQ },
98
+ compounds: false,
99
+ type: :boolean,
100
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
101
+ formatter: proc { |v| true }
102
+ }
103
+ ],
104
+ ['not_true'.freeze, {
105
+ arel_predicate: proc { |v| v ? NOT_EQ : EQ },
106
+ compounds: false,
107
+ type: :boolean,
108
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
109
+ formatter: proc { |v| true }
110
+ }
111
+ ],
112
+ ['false'.freeze, {
113
+ arel_predicate: proc { |v| v ? EQ : NOT_EQ },
114
+ compounds: false,
115
+ type: :boolean,
116
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
117
+ formatter: proc { |v| false }
118
+ }
119
+ ],
120
+ ['not_false'.freeze, {
121
+ arel_predicate: proc { |v| v ? NOT_EQ : EQ },
122
+ compounds: false,
123
+ type: :boolean,
124
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
125
+ formatter: proc { |v| false }
126
+ }
127
+ ],
128
+ ['present'.freeze, {
129
+ arel_predicate: proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
130
+ compounds: false,
131
+ type: :boolean,
132
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
133
+ formatter: proc { |v| [nil, ''.freeze].freeze }
134
+ }
135
+ ],
136
+ ['blank'.freeze, {
137
+ arel_predicate: proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
138
+ compounds: false,
139
+ type: :boolean,
140
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
141
+ formatter: proc { |v| [nil, ''.freeze].freeze }
142
+ }
143
+ ],
144
+ ['null'.freeze, {
145
+ arel_predicate: proc { |v| v ? EQ : NOT_EQ },
146
+ compounds: false,
147
+ type: :boolean,
148
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
149
+ formatter: proc { |v| nil }
150
+ }
151
+ ],
152
+ ['not_null'.freeze, {
153
+ arel_predicate: proc { |v| v ? NOT_EQ : EQ },
154
+ compounds: false,
155
+ type: :boolean,
156
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
157
+ formatter: proc { |v| nil } }
158
+ ]
159
+ ].freeze
160
+
161
+ module_function
162
+ # replace % \ to \% \\
163
+ def escape_wildcards(unescaped)
164
+ case ActiveRecord::Base.connection.adapter_name
165
+ when "Mysql2".freeze
166
+ # Necessary for MySQL
167
+ unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
168
+ when "PostGIS".freeze, "PostgreSQL".freeze
169
+ # Necessary for PostgreSQL
170
+ unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
171
+ else
172
+ unescaped
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,190 @@
1
+ require 'ransack/visitor'
2
+
3
+ module Ransack
4
+ class Context
5
+ attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
6
+ attr_accessor :auth_object, :search_key
7
+ attr_reader :arel_visitor
8
+
9
+ class << self
10
+
11
+ def for_class(klass, options = {})
12
+ if klass < ActiveRecord::Base
13
+ Adapters::ActiveRecord::Context.new(klass, options)
14
+ end
15
+ end
16
+
17
+ def for_object(object, options = {})
18
+ case object
19
+ when ActiveRecord::Relation
20
+ Adapters::ActiveRecord::Context.new(object.klass, options)
21
+ end
22
+ end
23
+
24
+ def for(object, options = {})
25
+ context =
26
+ if Class === object
27
+ for_class(object, options)
28
+ else
29
+ for_object(object, options)
30
+ end
31
+ context or raise ArgumentError,
32
+ "Don't know what context to use for #{object}"
33
+ end
34
+
35
+ end # << self
36
+
37
+ def initialize(object, options = {})
38
+ @object = relation_for(object)
39
+ @klass = @object.klass
40
+ @join_dependency = join_dependency(@object)
41
+ @join_type = options[:join_type] || Polyamorous::OuterJoin
42
+ @search_key = options[:search_key] || Ransack.options[:search_key]
43
+ @associations_pot = {}
44
+ @tables_pot = {}
45
+ @lock_associations = []
46
+
47
+ @base = @join_dependency.instance_variable_get(:@join_root)
48
+ end
49
+
50
+ def bind_pair_for(key)
51
+ @bind_pairs ||= {}
52
+
53
+ @bind_pairs[key] ||= begin
54
+ parent, attr_name = get_parent_and_attribute_name(key.to_s)
55
+ [parent, attr_name] if parent && attr_name
56
+ end
57
+ end
58
+
59
+ def klassify(obj)
60
+ if Class === obj && ::ActiveRecord::Base > obj
61
+ obj
62
+ elsif obj.respond_to? :klass
63
+ obj.klass
64
+ else
65
+ raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
66
+ end
67
+ end
68
+
69
+ # Convert a string representing a chain of associations and an attribute
70
+ # into the attribute itself
71
+ def contextualize(str)
72
+ parent, attr_name = bind_pair_for(str)
73
+ table_for(parent)[attr_name]
74
+ end
75
+
76
+ def chain_scope(scope, args)
77
+ return unless @klass.method(scope) && args != false
78
+ @object = if scope_arity(scope) < 1 && args == true
79
+ @object.public_send(scope)
80
+ elsif scope_arity(scope) == 1 && args.is_a?(Array)
81
+ # For scopes with arity 1, pass the array as a single argument instead of splatting
82
+ @object.public_send(scope, args)
83
+ else
84
+ @object.public_send(scope, *args)
85
+ end
86
+ end
87
+
88
+ def scope_arity(scope)
89
+ @klass.method(scope).arity
90
+ end
91
+
92
+ def bind(object, str)
93
+ return nil unless str
94
+ object.parent, object.attr_name = bind_pair_for(str)
95
+ end
96
+
97
+ def traverse(str, base = @base)
98
+ str ||= ''.freeze
99
+ segments = str.split(Constants::UNDERSCORE)
100
+ unless segments.empty?
101
+ remainder = []
102
+ found_assoc = nil
103
+ until found_assoc || segments.empty?
104
+ # Strip the _of_Model_type text from the association name, but hold
105
+ # onto it in klass, for use as the next base
106
+ assoc, klass = unpolymorphize_association(
107
+ segments.join(Constants::UNDERSCORE)
108
+ )
109
+ if found_assoc = get_association(assoc, base)
110
+ base = traverse(
111
+ remainder.join(Constants::UNDERSCORE), klass || found_assoc.klass
112
+ )
113
+ end
114
+
115
+ remainder.unshift segments.pop
116
+ end
117
+ unless found_assoc
118
+ raise(UntraversableAssociationError,
119
+ "No association matches #{str}")
120
+ end
121
+ end
122
+
123
+ klassify(base)
124
+ end
125
+
126
+ def association_path(str, base = @base)
127
+ base = klassify(base)
128
+ str ||= ''.freeze
129
+ path = []
130
+ segments = str.split(Constants::UNDERSCORE)
131
+ association_parts = []
132
+ unless segments.empty?
133
+ while !segments.empty? &&
134
+ !base.columns_hash[segments.join(Constants::UNDERSCORE)] &&
135
+ association_parts << segments.shift
136
+ assoc, klass = unpolymorphize_association(
137
+ association_parts.join(Constants::UNDERSCORE)
138
+ )
139
+ next unless found_assoc = get_association(assoc, base)
140
+ path += association_parts
141
+ association_parts = []
142
+ base = klassify(klass || found_assoc)
143
+ end
144
+ end
145
+
146
+ path.join(Constants::UNDERSCORE)
147
+ end
148
+
149
+ def unpolymorphize_association(str)
150
+ if (match = str.match(/_of_([^_]+?)_type$/))
151
+ [match.pre_match, Kernel.const_get(match.captures.first)]
152
+ else
153
+ [str, nil]
154
+ end
155
+ end
156
+
157
+ def ransackable_alias(str)
158
+ klass._ransack_aliases.fetch(str, klass._ransack_aliases.fetch(str.to_sym, str))
159
+ end
160
+
161
+ def ransackable_attribute?(str, klass)
162
+ klass.ransackable_attributes(auth_object).any? { |s| s.to_sym == str.to_sym } ||
163
+ klass.ransortable_attributes(auth_object).any? { |s| s.to_sym == str.to_sym }
164
+ end
165
+
166
+ def ransackable_association?(str, klass)
167
+ klass.ransackable_associations(auth_object).any? { |s| s.to_sym == str.to_sym }
168
+ end
169
+
170
+ def ransackable_scope?(str, klass)
171
+ klass.ransackable_scopes(auth_object).any? { |s| s.to_sym == str.to_sym }
172
+ end
173
+
174
+ def ransackable_scope_skip_sanitize_args?(str, klass)
175
+ klass.ransackable_scopes_skip_sanitize_args.any? { |s| s.to_sym == str.to_sym }
176
+ end
177
+
178
+ def searchable_attributes(str = ''.freeze)
179
+ traverse(str).ransackable_attributes(auth_object)
180
+ end
181
+
182
+ def sortable_attributes(str = ''.freeze)
183
+ traverse(str).ransortable_attributes(auth_object)
184
+ end
185
+
186
+ def searchable_associations(str = ''.freeze)
187
+ traverse(str).ransackable_associations(auth_object)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,277 @@
1
+ require 'action_view'
2
+
3
+ module ActionView::Helpers::Tags
4
+ # TODO: Find a better way to solve this issue!
5
+ # This patch is needed since this Rails commit:
6
+ # https://github.com/rails/rails/commit/c1a118a
7
+ class Base
8
+ private
9
+
10
+ def value
11
+ if @allow_method_names_outside_object
12
+ object.send @method_name if object && object.respond_to?(@method_name, true)
13
+ else
14
+ object.send @method_name if object
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ RANSACK_FORM_BUILDER = 'RANSACK_FORM_BUILDER'.freeze
21
+
22
+ require 'simple_form' if
23
+ (ENV[RANSACK_FORM_BUILDER] || ''.freeze).match('SimpleForm'.freeze)
24
+
25
+ module Ransack
26
+ module Helpers
27
+ class FormBuilder < (ENV[RANSACK_FORM_BUILDER].try(:constantize) ||
28
+ ActionView::Helpers::FormBuilder)
29
+
30
+ def label(method, *args, &block)
31
+ options = args.extract_options!
32
+ text = args.first
33
+ i18n = options[:i18n] || {}
34
+ text ||= object.translate(
35
+ method, i18n.reverse_merge(include_associations: true)
36
+ ) if object.respond_to? :translate
37
+ super(method, text, options, &block)
38
+ end
39
+
40
+ def submit(value = nil, options = {})
41
+ value, options = nil, value if value.is_a?(Hash)
42
+ value ||= Translate.word(:search).titleize
43
+ super(value, options)
44
+ end
45
+
46
+ def attribute_select(options = nil, html_options = nil, action = nil)
47
+ options ||= {}
48
+ html_options ||= {}
49
+ action ||= Constants::SEARCH
50
+ default = options.delete(:default)
51
+ raise ArgumentError, formbuilder_error_message(
52
+ "#{action}_select") unless object.respond_to?(:context)
53
+ options[:include_blank] = true unless options.has_key?(:include_blank)
54
+ bases = [''.freeze].freeze + association_array(options[:associations])
55
+ if bases.size > 1
56
+ collection = attribute_collection_for_bases(action, bases)
57
+ object.name ||= default if can_use_default?(
58
+ default, :name, mapped_values(collection.flatten(2))
59
+ )
60
+ template_grouped_collection_select(collection, options, html_options)
61
+ else
62
+ collection = collection_for_base(action, bases.first)
63
+ object.name ||= default if can_use_default?(
64
+ default, :name, mapped_values(collection)
65
+ )
66
+ template_collection_select(:name, collection, options, html_options)
67
+ end
68
+ end
69
+
70
+ def sort_direction_select(options = {}, html_options = {})
71
+ unless object.respond_to?(:context)
72
+ raise ArgumentError,
73
+ formbuilder_error_message('sort_direction'.freeze)
74
+ end
75
+ template_collection_select(:dir, sort_array, options, html_options)
76
+ end
77
+
78
+ def sort_select(options = {}, html_options = {})
79
+ attribute_select(options, html_options, 'sort'.freeze) +
80
+ sort_direction_select(options, html_options)
81
+ end
82
+
83
+ def sort_fields(*args, &block)
84
+ search_fields(:s, args, block)
85
+ end
86
+
87
+ def sort_link(attribute, *args)
88
+ @template.sort_link @object, attribute, *args
89
+ end
90
+
91
+ def sort_url(attribute, *args)
92
+ @template.sort_url @object, attribute, *args
93
+ end
94
+
95
+ def condition_fields(*args, &block)
96
+ search_fields(:c, args, block)
97
+ end
98
+
99
+ def grouping_fields(*args, &block)
100
+ search_fields(:g, args, block)
101
+ end
102
+
103
+ def attribute_fields(*args, &block)
104
+ search_fields(:a, args, block)
105
+ end
106
+
107
+ def predicate_fields(*args, &block)
108
+ search_fields(:p, args, block)
109
+ end
110
+
111
+ def value_fields(*args, &block)
112
+ search_fields(:v, args, block)
113
+ end
114
+
115
+ def search_fields(name, args, block)
116
+ args << {} unless args.last.is_a?(Hash)
117
+ args.last[:builder] ||= options[:builder]
118
+ args.last[:parent_builder] = self
119
+ options = args.extract_options!
120
+ objects = args.shift
121
+ objects ||= @object.send(name)
122
+ objects = [objects] unless Array === objects
123
+ name = "#{options[:object_name] || object_name}[#{name}]"
124
+ objects.inject(ActiveSupport::SafeBuffer.new) do |output, child|
125
+ output << @template.fields_for("#{name}[#{options[:child_index] ||
126
+ nested_child_index(name)}]", child, options, &block)
127
+ end
128
+ end
129
+
130
+ def predicate_select(options = {}, html_options = {})
131
+ options[:compounds] = true if options[:compounds].nil?
132
+ default = options.delete(:default) || Constants::CONT
133
+
134
+ keys =
135
+ if options[:compounds]
136
+ Predicate.names
137
+ else
138
+ Predicate.names.reject { |k| k.match(/_(any|all)$/) }
139
+ end
140
+ if only = options[:only]
141
+ if only.respond_to? :call
142
+ keys = keys.select { |k| only.call(k) }
143
+ else
144
+ only = Array.wrap(only).map(&:to_s)
145
+ keys = keys.select {
146
+ |k| only.include? k.sub(/_(any|all)$/, ''.freeze)
147
+ }
148
+ end
149
+ end
150
+ collection = keys.map { |k| [k, Translate.predicate(k)] }
151
+ object.predicate ||= Predicate.named(default) if
152
+ can_use_default?(default, :predicate, keys)
153
+ template_collection_select(:p, collection, options, html_options)
154
+ end
155
+
156
+ def combinator_select(options = {}, html_options = {})
157
+ template_collection_select(
158
+ :m, combinator_choices, options, html_options)
159
+ end
160
+
161
+ private
162
+
163
+ def template_grouped_collection_select(collection, options, html_options)
164
+ @template.grouped_collection_select(
165
+ @object_name, :name, collection, :last, :first, :first, :last,
166
+ objectify_options(options), @default_options.merge(html_options)
167
+ )
168
+ end
169
+
170
+ def template_collection_select(name, collection, options, html_options)
171
+ @template.collection_select(
172
+ @object_name, name, collection, :first, :last,
173
+ objectify_options(options), @default_options.merge(html_options)
174
+ )
175
+ end
176
+
177
+ def can_use_default?(default, attribute, values)
178
+ object.respond_to?("#{attribute}=") && default &&
179
+ values.include?(default.to_s)
180
+ end
181
+
182
+ def mapped_values(values)
183
+ values.map { |v| v.is_a?(Array) ? v.first : nil }.compact
184
+ end
185
+
186
+ def sort_array
187
+ [
188
+ ['asc'.freeze, object.translate('asc'.freeze)].freeze,
189
+ ['desc'.freeze, object.translate('desc'.freeze)].freeze
190
+ ].freeze
191
+ end
192
+
193
+ def combinator_choices
194
+ if Nodes::Condition === object
195
+ [
196
+ [Constants::OR, Translate.word(:any)],
197
+ [Constants::AND, Translate.word(:all)]
198
+ ]
199
+ else
200
+ [
201
+ [Constants::AND, Translate.word(:all)],
202
+ [Constants::OR, Translate.word(:any)]
203
+ ]
204
+ end
205
+ end
206
+
207
+ def association_array(obj, prefix = nil)
208
+ ([prefix] + association_object(obj))
209
+ .compact
210
+ .flat_map { |v| [prefix, v].compact.join(Constants::UNDERSCORE) }
211
+ end
212
+
213
+ def association_object(obj)
214
+ case obj
215
+ when Array
216
+ obj
217
+ when Hash
218
+ association_hash(obj)
219
+ else
220
+ [obj]
221
+ end
222
+ end
223
+
224
+ def association_hash(obj)
225
+ obj.map do |key, value|
226
+ case value
227
+ when Array, Hash
228
+ association_array(value, key.to_s)
229
+ else
230
+ [key.to_s, [key, value].join(Constants::UNDERSCORE)]
231
+ end
232
+ end
233
+ end
234
+
235
+ def attribute_collection_for_bases(action, bases)
236
+ bases.map { |base| get_attribute_element(action, base) }.compact
237
+ end
238
+
239
+ def get_attribute_element(action, base)
240
+ begin
241
+ [
242
+ Translate.association(base, context: object.context),
243
+ collection_for_base(action, base)
244
+ ]
245
+ rescue UntraversableAssociationError
246
+ nil
247
+ end
248
+ end
249
+
250
+ def attribute_collection_for_base(attributes, base = nil)
251
+ attributes.map do |c|
252
+ [
253
+ attr_from_base_and_column(base, c),
254
+ Translate.attribute(
255
+ attr_from_base_and_column(base, c), context: object.context
256
+ )
257
+ ]
258
+ end
259
+ end
260
+
261
+ def collection_for_base(action, base)
262
+ attribute_collection_for_base(
263
+ object.context.send("#{action}able_attributes", base), base)
264
+ end
265
+
266
+ def attr_from_base_and_column(base, column)
267
+ [base, column].reject(&:blank?).join(Constants::UNDERSCORE)
268
+ end
269
+
270
+ def formbuilder_error_message(action)
271
+ "#{action.sub(Constants::SEARCH, Constants::ATTRIBUTE)
272
+ } must be called inside a search FormBuilder!"
273
+ end
274
+
275
+ end
276
+ end
277
+ end