ransack 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.travis.yml +12 -4
- data/CONTRIBUTING.md +10 -4
- data/Gemfile +12 -9
- data/README.md +46 -11
- data/lib/ransack.rb +4 -2
- data/lib/ransack/adapters/active_record.rb +1 -1
- data/lib/ransack/adapters/active_record/3.0/compat.rb +16 -6
- data/lib/ransack/adapters/active_record/3.0/context.rb +32 -16
- data/lib/ransack/adapters/active_record/3.1/context.rb +32 -15
- data/lib/ransack/adapters/active_record/3.2/context.rb +1 -1
- data/lib/ransack/adapters/active_record/base.rb +9 -6
- data/lib/ransack/adapters/active_record/context.rb +193 -2
- data/lib/ransack/configuration.rb +4 -4
- data/lib/ransack/constants.rb +81 -18
- data/lib/ransack/context.rb +27 -12
- data/lib/ransack/helpers/form_builder.rb +126 -91
- data/lib/ransack/helpers/form_helper.rb +34 -12
- data/lib/ransack/naming.rb +2 -1
- data/lib/ransack/nodes/attribute.rb +6 -4
- data/lib/ransack/nodes/bindable.rb +3 -1
- data/lib/ransack/nodes/condition.rb +40 -27
- data/lib/ransack/nodes/grouping.rb +19 -13
- data/lib/ransack/nodes/node.rb +3 -3
- data/lib/ransack/nodes/sort.rb +5 -3
- data/lib/ransack/nodes/value.rb +2 -2
- data/lib/ransack/predicate.rb +18 -9
- data/lib/ransack/ransacker.rb +4 -4
- data/lib/ransack/search.rb +9 -12
- data/lib/ransack/translate.rb +42 -21
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +4 -4
- data/ransack.gemspec +17 -7
- data/spec/blueprints/notes.rb +2 -0
- data/spec/blueprints/people.rb +4 -1
- data/spec/console.rb +3 -3
- data/spec/ransack/adapters/active_record/base_spec.rb +149 -22
- data/spec/ransack/adapters/active_record/context_spec.rb +5 -5
- data/spec/ransack/configuration_spec.rb +17 -8
- data/spec/ransack/dependencies_spec.rb +8 -0
- data/spec/ransack/helpers/form_builder_spec.rb +37 -14
- data/spec/ransack/helpers/form_helper_spec.rb +5 -5
- data/spec/ransack/predicate_spec.rb +6 -3
- data/spec/ransack/search_spec.rb +95 -73
- data/spec/ransack/translate_spec.rb +14 -0
- data/spec/spec_helper.rb +14 -8
- data/spec/support/en.yml +6 -0
- data/spec/support/schema.rb +76 -31
- metadata +48 -29
| @@ -1,62 +1,62 @@ | |
| 1 1 | 
             
            require 'action_view'
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'simple_form' if
         | 
| 4 | 
            +
              (ENV['RANSACK_FORM_BUILDER'] || '').match('SimpleForm')
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            module Ransack
         | 
| 4 7 | 
             
              module Helpers
         | 
| 5 | 
            -
                class FormBuilder <  | 
| 8 | 
            +
                class FormBuilder < (ENV['RANSACK_FORM_BUILDER'].try(:constantize) ||
         | 
| 9 | 
            +
                  ActionView::Helpers::FormBuilder)
         | 
| 10 | 
            +
             | 
| 6 11 | 
             
                  def label(method, *args, &block)
         | 
| 7 12 | 
             
                    options = args.extract_options!
         | 
| 8 13 | 
             
                    text = args.first
         | 
| 9 14 | 
             
                    i18n = options[:i18n] || {}
         | 
| 10 | 
            -
                    text ||= object.translate( | 
| 15 | 
            +
                    text ||= object.translate(
         | 
| 16 | 
            +
                      method, i18n.reverse_merge(include_associations: true)
         | 
| 17 | 
            +
                      ) if object.respond_to? :translate
         | 
| 11 18 | 
             
                    super(method, text, options, &block)
         | 
| 12 19 | 
             
                  end
         | 
| 13 20 |  | 
| 14 | 
            -
                  def submit(value=nil, options={})
         | 
| 21 | 
            +
                  def submit(value = nil, options = {})
         | 
| 15 22 | 
             
                    value, options = nil, value if value.is_a?(Hash)
         | 
| 16 23 | 
             
                    value ||= Translate.word(:search).titleize
         | 
| 17 24 | 
             
                    super(value, options)
         | 
| 18 25 | 
             
                  end
         | 
| 19 26 |  | 
| 20 | 
            -
                  def attribute_select(options =  | 
| 21 | 
            -
                     | 
| 27 | 
            +
                  def attribute_select(options = nil, html_options = nil, action = nil)
         | 
| 28 | 
            +
                    options = options || {}
         | 
| 29 | 
            +
                    html_options = html_options || {}
         | 
| 30 | 
            +
                    action = action || 'search'
         | 
| 31 | 
            +
                    default = options.delete(:default)
         | 
| 32 | 
            +
                    raise ArgumentError, formbuilder_error_message(
         | 
| 33 | 
            +
                      "#{action}_select") unless object.respond_to?(:context)
         | 
| 22 34 | 
             
                    options[:include_blank] = true unless options.has_key?(:include_blank)
         | 
| 23 35 | 
             
                    bases = [''] + association_array(options[:associations])
         | 
| 24 36 | 
             
                    if bases.size > 1
         | 
| 25 | 
            -
                       | 
| 26 | 
            -
             | 
| 27 | 
            -
                         | 
| 28 | 
            -
             | 
| 37 | 
            +
                      collection = attribute_collection_for_bases(action, bases)
         | 
| 38 | 
            +
                      object.name ||= default if can_use_default?(
         | 
| 39 | 
            +
                        default, :name, mapped_values(collection.flatten(2))
         | 
| 40 | 
            +
                        )
         | 
| 41 | 
            +
                      template_grouped_collection_select(collection, options, html_options)
         | 
| 29 42 | 
             
                    else
         | 
| 30 | 
            -
                      collection =  | 
| 31 | 
            -
                       | 
| 32 | 
            -
                         | 
| 33 | 
            -
                         | 
| 34 | 
            -
                      )
         | 
| 43 | 
            +
                      collection = collection_for_base(action, bases.first)
         | 
| 44 | 
            +
                      object.name ||= default if can_use_default?(
         | 
| 45 | 
            +
                        default, :name, mapped_values(collection)
         | 
| 46 | 
            +
                        )
         | 
| 47 | 
            +
                      template_collection_select(:name, collection, options, html_options)
         | 
| 35 48 | 
             
                    end
         | 
| 36 49 | 
             
                  end
         | 
| 37 50 |  | 
| 51 | 
            +
                  def sort_direction_select(options = {}, html_options = {})
         | 
| 52 | 
            +
                    raise ArgumentError, formbuilder_error_message(
         | 
| 53 | 
            +
                      'sort_direction') unless object.respond_to?(:context)
         | 
| 54 | 
            +
                    template_collection_select(:dir, sort_array, options, html_options)
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 38 57 | 
             
                  def sort_select(options = {}, html_options = {})
         | 
| 39 | 
            -
                     | 
| 40 | 
            -
                    options | 
| 41 | 
            -
                    bases = [''] + association_array(options[:associations])
         | 
| 42 | 
            -
                    if bases.size > 1
         | 
| 43 | 
            -
                      @template.grouped_collection_select(
         | 
| 44 | 
            -
                        @object_name, :name, sortable_attribute_collection_for_bases(bases), :last, :first, :first, :last,
         | 
| 45 | 
            -
                        objectify_options(options), @default_options.merge(html_options)
         | 
| 46 | 
            -
                      ) + @template.collection_select(
         | 
| 47 | 
            -
                        @object_name, :dir, [['asc', object.translate('asc')], ['desc', object.translate('desc')]], :first, :last,
         | 
| 48 | 
            -
                        objectify_options(options), @default_options.merge(html_options)
         | 
| 49 | 
            -
                      )
         | 
| 50 | 
            -
                    else
         | 
| 51 | 
            -
                      collection = sortable_attribute_collection_for_base(bases.first)
         | 
| 52 | 
            -
                      @template.collection_select(
         | 
| 53 | 
            -
                        @object_name, :name, collection, :first, :last,
         | 
| 54 | 
            -
                        objectify_options(options), @default_options.merge(html_options)
         | 
| 55 | 
            -
                      ) + @template.collection_select(
         | 
| 56 | 
            -
                        @object_name, :dir, [['asc', object.translate('asc')], ['desc', object.translate('desc')]], :first, :last,
         | 
| 57 | 
            -
                        objectify_options(options), @default_options.merge(html_options)
         | 
| 58 | 
            -
                      )
         | 
| 59 | 
            -
                    end
         | 
| 58 | 
            +
                    attribute_select(options, html_options, 'sort') +
         | 
| 59 | 
            +
                    sort_direction_select(options, html_options)
         | 
| 60 60 | 
             
                  end
         | 
| 61 61 |  | 
| 62 62 | 
             
                  def sort_fields(*args, &block)
         | 
| @@ -98,37 +98,70 @@ module Ransack | |
| 98 98 | 
             
                    name = "#{options[:object_name] || object_name}[#{name}]"
         | 
| 99 99 | 
             
                    output = ActiveSupport::SafeBuffer.new
         | 
| 100 100 | 
             
                    objects.each do |child|
         | 
| 101 | 
            -
                      output << @template.fields_for("#{name}[#{ | 
| 101 | 
            +
                      output << @template.fields_for("#{name}[#{
         | 
| 102 | 
            +
                        options[:child_index] || nested_child_index(name)
         | 
| 103 | 
            +
                        }]", child, options, &block)
         | 
| 102 104 | 
             
                    end
         | 
| 103 105 | 
             
                    output
         | 
| 104 106 | 
             
                  end
         | 
| 105 107 |  | 
| 106 108 | 
             
                  def predicate_select(options = {}, html_options = {})
         | 
| 107 109 | 
             
                    options[:compounds] = true if options[:compounds].nil?
         | 
| 108 | 
            -
                     | 
| 110 | 
            +
                    if ::ActiveRecord::VERSION::STRING >= "4"
         | 
| 111 | 
            +
                      default = options.delete(:default) || 'cont'
         | 
| 112 | 
            +
                    else
         | 
| 113 | 
            +
                      default = options.delete(:default) || 'eq'
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    keys = options[:compounds] ? Predicate.names : 
         | 
| 117 | 
            +
                      Predicate.names.reject { |k| k.match(/_(any|all)$/) }
         | 
| 109 118 | 
             
                    if only = options[:only]
         | 
| 110 119 | 
             
                      if only.respond_to? :call
         | 
| 111 | 
            -
                        keys = keys.select {|k| only.call(k)}
         | 
| 120 | 
            +
                        keys = keys.select { |k| only.call(k) }
         | 
| 112 121 | 
             
                      else
         | 
| 113 122 | 
             
                        only = Array.wrap(only).map(&:to_s)
         | 
| 114 | 
            -
                        keys = keys.select {|k| only.include? k.sub(/_(any|all)$/, '')}
         | 
| 123 | 
            +
                        keys = keys.select { |k| only.include? k.sub(/_(any|all)$/, '') }
         | 
| 115 124 | 
             
                      end
         | 
| 116 125 | 
             
                    end
         | 
| 126 | 
            +
                    collection = keys.map { |k| [k, Translate.predicate(k)] }
         | 
| 127 | 
            +
                    object.predicate ||= Predicate.named(default) if can_use_default?(
         | 
| 128 | 
            +
                      default, :predicate, keys
         | 
| 129 | 
            +
                      )
         | 
| 130 | 
            +
                    template_collection_select(:p, collection, options, html_options)
         | 
| 131 | 
            +
                  end
         | 
| 117 132 |  | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 133 | 
            +
                  def combinator_select(options = {}, html_options = {})
         | 
| 134 | 
            +
                    template_collection_select(:m, combinator_choices, options, html_options)
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  private
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  def template_grouped_collection_select(collection, options, html_options)
         | 
| 140 | 
            +
                    @template.grouped_collection_select(
         | 
| 141 | 
            +
                      @object_name, :name, collection, :last, :first, :first, :last,
         | 
| 120 142 | 
             
                      objectify_options(options), @default_options.merge(html_options)
         | 
| 121 | 
            -
             | 
| 143 | 
            +
                      )
         | 
| 122 144 | 
             
                  end
         | 
| 123 145 |  | 
| 124 | 
            -
                  def  | 
| 146 | 
            +
                  def template_collection_select(name, collection, options, html_options)
         | 
| 125 147 | 
             
                    @template.collection_select(
         | 
| 126 | 
            -
                      @object_name,  | 
| 148 | 
            +
                      @object_name, name, collection, :first, :last,
         | 
| 127 149 | 
             
                      objectify_options(options), @default_options.merge(html_options)
         | 
| 128 | 
            -
             | 
| 150 | 
            +
                      )
         | 
| 129 151 | 
             
                  end
         | 
| 130 152 |  | 
| 131 | 
            -
                   | 
| 153 | 
            +
                  def can_use_default?(default, attribute, values)
         | 
| 154 | 
            +
                    object.respond_to?("#{attribute}=") && default &&
         | 
| 155 | 
            +
                      values.include?(default.to_s)
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  def mapped_values(values)
         | 
| 159 | 
            +
                    values.map { |v| v.is_a?(Array) ? v.first : nil }.compact
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  def sort_array
         | 
| 163 | 
            +
                    [['asc', object.translate('asc')], ['desc', object.translate('desc')]]
         | 
| 164 | 
            +
                  end
         | 
| 132 165 |  | 
| 133 166 | 
             
                  def combinator_choices
         | 
| 134 167 | 
             
                    if Nodes::Condition === object
         | 
| @@ -139,70 +172,72 @@ module Ransack | |
| 139 172 | 
             
                  end
         | 
| 140 173 |  | 
| 141 174 | 
             
                  def association_array(obj, prefix = nil)
         | 
| 142 | 
            -
                    ([prefix] +  | 
| 175 | 
            +
                    ([prefix] + association_object(obj))
         | 
| 176 | 
            +
                    .compact
         | 
| 177 | 
            +
                    .flatten
         | 
| 178 | 
            +
                    .map { |v| [prefix, v].compact.join('_') }
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                  def association_object(obj)
         | 
| 182 | 
            +
                    case obj
         | 
| 143 183 | 
             
                    when Array
         | 
| 144 184 | 
             
                      obj
         | 
| 145 185 | 
             
                    when Hash
         | 
| 146 | 
            -
                      obj | 
| 147 | 
            -
                        case value
         | 
| 148 | 
            -
                        when Array, Hash
         | 
| 149 | 
            -
                          association_array(value, key.to_s)
         | 
| 150 | 
            -
                        else
         | 
| 151 | 
            -
                          [key.to_s, [key, value].join('_')]
         | 
| 152 | 
            -
                        end
         | 
| 153 | 
            -
                      end
         | 
| 186 | 
            +
                      association_hash(obj)
         | 
| 154 187 | 
             
                    else
         | 
| 155 188 | 
             
                      [obj]
         | 
| 156 | 
            -
                    end | 
| 157 | 
            -
                    compact.flatten.map { |v| [prefix, v].compact.join('_') }
         | 
| 189 | 
            +
                    end
         | 
| 158 190 | 
             
                  end
         | 
| 159 191 |  | 
| 160 | 
            -
                  def  | 
| 161 | 
            -
                     | 
| 192 | 
            +
                  def association_hash(obj)
         | 
| 193 | 
            +
                    obj.map do |key, value|
         | 
| 194 | 
            +
                      case value
         | 
| 195 | 
            +
                      when Array, Hash
         | 
| 196 | 
            +
                        association_array(value, key.to_s)
         | 
| 197 | 
            +
                      else
         | 
| 198 | 
            +
                        [key.to_s, [key, value].join('_')]
         | 
| 199 | 
            +
                      end
         | 
| 200 | 
            +
                    end
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  def attribute_collection_for_bases(action, bases)
         | 
| 204 | 
            +
                    bases.map { |base| get_attribute_element(action, base) }.compact
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                  def get_attribute_element(action, base)
         | 
| 208 | 
            +
                    begin
         | 
| 209 | 
            +
                      [Translate.association(base, context: object.context),
         | 
| 210 | 
            +
                        collection_for_base(action, base)]
         | 
| 211 | 
            +
                    rescue UntraversableAssociationError => e
         | 
| 212 | 
            +
                      nil
         | 
| 213 | 
            +
                    end
         | 
| 162 214 | 
             
                  end
         | 
| 163 215 |  | 
| 164 | 
            -
                  def attribute_collection_for_base(attributes, base=nil)
         | 
| 216 | 
            +
                  def attribute_collection_for_base(attributes, base = nil)
         | 
| 165 217 | 
             
                    attributes.map do |c|
         | 
| 166 | 
            -
                      [
         | 
| 167 | 
            -
                         | 
| 168 | 
            -
             | 
| 218 | 
            +
                      [attr_from_base_and_column(base, c),
         | 
| 219 | 
            +
                        Translate.attribute(
         | 
| 220 | 
            +
                          attr_from_base_and_column(base, c),
         | 
| 221 | 
            +
                          context: object.context
         | 
| 222 | 
            +
                        )
         | 
| 169 223 | 
             
                      ]
         | 
| 170 224 | 
             
                    end
         | 
| 171 225 | 
             
                  end
         | 
| 172 226 |  | 
| 173 | 
            -
                  def  | 
| 174 | 
            -
                    attribute_collection_for_base( | 
| 227 | 
            +
                  def collection_for_base(action, base)
         | 
| 228 | 
            +
                    attribute_collection_for_base(
         | 
| 229 | 
            +
                      object.context.send("#{action}able_attributes", base), base)
         | 
| 175 230 | 
             
                  end
         | 
| 176 231 |  | 
| 177 | 
            -
                  def  | 
| 178 | 
            -
                     | 
| 232 | 
            +
                  def attr_from_base_and_column(base, column)
         | 
| 233 | 
            +
                    [base, column].reject { |v| v.blank? }.join('_')
         | 
| 179 234 | 
             
                  end
         | 
| 180 235 |  | 
| 181 | 
            -
                  def  | 
| 182 | 
            -
                     | 
| 183 | 
            -
                       | 
| 184 | 
            -
                      [
         | 
| 185 | 
            -
                        Translate.association(base, :context => object.context),
         | 
| 186 | 
            -
                        sortable_attribute_collection_for_base(base)
         | 
| 187 | 
            -
                      ]
         | 
| 188 | 
            -
                      rescue UntraversableAssociationError => e
         | 
| 189 | 
            -
                        nil
         | 
| 190 | 
            -
                      end
         | 
| 191 | 
            -
                    end.compact
         | 
| 236 | 
            +
                  def formbuilder_error_message(action)
         | 
| 237 | 
            +
                    "#{action.sub('search', 'attribute')
         | 
| 238 | 
            +
                      } must be called inside a search FormBuilder!"
         | 
| 192 239 | 
             
                  end
         | 
| 193 240 |  | 
| 194 | 
            -
                  def searchable_attribute_collection_for_bases(bases)
         | 
| 195 | 
            -
                    bases.map do |base|
         | 
| 196 | 
            -
                      begin
         | 
| 197 | 
            -
                      [
         | 
| 198 | 
            -
                        Translate.association(base, :context => object.context),
         | 
| 199 | 
            -
                        searchable_attribute_collection_for_base(base)
         | 
| 200 | 
            -
                      ]
         | 
| 201 | 
            -
                      rescue UntraversableAssociationError => e
         | 
| 202 | 
            -
                        nil
         | 
| 203 | 
            -
                      end
         | 
| 204 | 
            -
                    end.compact
         | 
| 205 | 
            -
                  end
         | 
| 206 241 | 
             
                end
         | 
| 207 242 | 
             
              end
         | 
| 208 | 
            -
            end
         | 
| 243 | 
            +
            end
         | 
| @@ -6,16 +6,24 @@ module Ransack | |
| 6 6 | 
             
                    if record.is_a?(Ransack::Search)
         | 
| 7 7 | 
             
                      search = record
         | 
| 8 8 | 
             
                      options[:url] ||= polymorphic_path(search.klass)
         | 
| 9 | 
            -
                    elsif record.is_a?(Array) && | 
| 10 | 
            -
             | 
| 9 | 
            +
                    elsif record.is_a?(Array) &&
         | 
| 10 | 
            +
                        (search = record.detect { |o| o.is_a?(Ransack::Search) })
         | 
| 11 | 
            +
                      options[:url] ||= polymorphic_path(
         | 
| 12 | 
            +
                        record.map { |o| o.is_a?(Ransack::Search) ? o.klass : o }
         | 
| 13 | 
            +
                        )
         | 
| 11 14 | 
             
                    else
         | 
| 12 | 
            -
                      raise ArgumentError, | 
| 15 | 
            +
                      raise ArgumentError,
         | 
| 16 | 
            +
                        "No Ransack::Search object was provided to search_form_for!"
         | 
| 13 17 | 
             
                    end
         | 
| 14 18 | 
             
                    options[:html] ||= {}
         | 
| 15 19 | 
             
                    html_options = {
         | 
| 16 | 
            -
                      : | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 20 | 
            +
                      class: options[:class].present? ?
         | 
| 21 | 
            +
                        "#{options[:class]}" :
         | 
| 22 | 
            +
                        "#{search.klass.to_s.underscore}_search",
         | 
| 23 | 
            +
                      id: options[:id].present? ?
         | 
| 24 | 
            +
                        "#{options[:id]}" :
         | 
| 25 | 
            +
                        "#{search.klass.to_s.underscore}_search",
         | 
| 26 | 
            +
                      method: :get
         | 
| 19 27 | 
             
                    }
         | 
| 20 28 | 
             
                    options[:as] ||= 'q'
         | 
| 21 29 | 
             
                    options[:html].reverse_merge!(html_options)
         | 
| @@ -31,15 +39,23 @@ module Ransack | |
| 31 39 | 
             
                      search = search.first
         | 
| 32 40 | 
             
                    end
         | 
| 33 41 |  | 
| 34 | 
            -
                    raise TypeError, "First argument must be a Ransack::Search!" unless | 
| 42 | 
            +
                    raise TypeError, "First argument must be a Ransack::Search!" unless
         | 
| 43 | 
            +
                      Search === search
         | 
| 35 44 |  | 
| 36 | 
            -
                    search_params = params[search.context.search_key] || | 
| 45 | 
            +
                    search_params = params[search.context.search_key] ||
         | 
| 46 | 
            +
                      {}.with_indifferent_access
         | 
| 37 47 |  | 
| 38 48 | 
             
                    attr_name = attribute.to_s
         | 
| 39 49 |  | 
| 40 | 
            -
                    name = ( | 
| 50 | 
            +
                    name = (
         | 
| 51 | 
            +
                      if args.size > 0 && !args.first.is_a?(Hash)
         | 
| 52 | 
            +
                        args.shift.to_s
         | 
| 53 | 
            +
                      else
         | 
| 54 | 
            +
                        Translate.attribute(attr_name, context: search.context)
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
                      )
         | 
| 41 57 |  | 
| 42 | 
            -
                    if existing_sort = search.sorts.detect {|s| s.name == attr_name}
         | 
| 58 | 
            +
                    if existing_sort = search.sorts.detect { |s| s.name == attr_name }
         | 
| 43 59 | 
             
                      prev_attr, prev_dir = existing_sort.name, existing_sort.dir
         | 
| 44 60 | 
             
                    end
         | 
| 45 61 |  | 
| @@ -57,7 +73,8 @@ module Ransack | |
| 57 73 | 
             
                    css = ['sort_link', current_dir].compact.join(' ')
         | 
| 58 74 | 
             
                    html_options[:class] = [css, html_options[:class]].compact.join(' ')
         | 
| 59 75 | 
             
                    query_hash = {}
         | 
| 60 | 
            -
                    query_hash[search.context.search_key] = search_params | 
| 76 | 
            +
                    query_hash[search.context.search_key] = search_params
         | 
| 77 | 
            +
                      .merge(s: "#{attr_name} #{new_dir}")
         | 
| 61 78 | 
             
                    options.merge!(query_hash)
         | 
| 62 79 | 
             
                    options_for_url = params.merge options
         | 
| 63 80 |  | 
| @@ -67,9 +84,14 @@ module Ransack | |
| 67 84 | 
             
                      url_for(options_for_url)
         | 
| 68 85 | 
             
                    end
         | 
| 69 86 |  | 
| 70 | 
            -
                    link_to | 
| 87 | 
            +
                    link_to(
         | 
| 88 | 
            +
                      [ERB::Util.h(name), order_indicator_for(current_dir)]
         | 
| 89 | 
            +
                        .compact
         | 
| 90 | 
            +
                        .join(' ')
         | 
| 91 | 
            +
                        .html_safe,
         | 
| 71 92 | 
             
                      url,
         | 
| 72 93 | 
             
                      html_options
         | 
| 94 | 
            +
                      )
         | 
| 73 95 | 
             
                  end
         | 
| 74 96 |  | 
| 75 97 | 
             
                  private
         | 
    
        data/lib/ransack/naming.rb
    CHANGED
    
    | @@ -23,7 +23,8 @@ module Ransack | |
| 23 23 | 
             
              end
         | 
| 24 24 |  | 
| 25 25 | 
             
              class Name < String
         | 
| 26 | 
            -
                attr_reader :singular, :plural, :element, :collection, :partial_path, | 
| 26 | 
            +
                attr_reader :singular, :plural, :element, :collection, :partial_path,
         | 
| 27 | 
            +
                            :human, :param_key, :route_key, :i18n_key
         | 
| 27 28 | 
             
                alias_method :cache_key, :collection
         | 
| 28 29 |  | 
| 29 30 | 
             
                def initialize
         | 
| @@ -5,8 +5,8 @@ module Ransack | |
| 5 5 |  | 
| 6 6 | 
             
                  attr_reader :name
         | 
| 7 7 |  | 
| 8 | 
            -
                  delegate :blank?, :present?, :==, : | 
| 9 | 
            -
                  delegate :engine, : | 
| 8 | 
            +
                  delegate :blank?, :present?, :==, to: :name
         | 
| 9 | 
            +
                  delegate :engine, to: :context
         | 
| 10 10 |  | 
| 11 11 | 
             
                  def initialize(context, name = nil)
         | 
| 12 12 | 
             
                    super(context)
         | 
| @@ -19,7 +19,9 @@ module Ransack | |
| 19 19 | 
             
                  end
         | 
| 20 20 |  | 
| 21 21 | 
             
                  def valid?
         | 
| 22 | 
            -
                    bound? && attr
         | 
| 22 | 
            +
                    bound? && attr &&
         | 
| 23 | 
            +
                    context.klassify(parent).ransackable_attributes(context.auth_object)
         | 
| 24 | 
            +
                    .include?(attr_name)
         | 
| 23 25 | 
             
                  end
         | 
| 24 26 |  | 
| 25 27 | 
             
                  def type
         | 
| @@ -50,4 +52,4 @@ module Ransack | |
| 50 52 |  | 
| 51 53 | 
             
                end
         | 
| 52 54 | 
             
              end
         | 
| 53 | 
            -
            end
         | 
| 55 | 
            +
            end
         | 
| @@ -5,7 +5,9 @@ module Ransack | |
| 5 5 | 
             
                  attr_accessor :parent, :attr_name
         | 
| 6 6 |  | 
| 7 7 | 
             
                  def attr
         | 
| 8 | 
            -
                    @attr ||= ransacker ? | 
| 8 | 
            +
                    @attr ||= ransacker ?
         | 
| 9 | 
            +
                      ransacker.attr_from(self) :
         | 
| 10 | 
            +
                      context.table_for(parent)[attr_name]
         | 
| 9 11 | 
             
                  end
         | 
| 10 12 | 
             
                  alias :arel_attribute :attr
         | 
| 11 13 |  | 
| @@ -2,9 +2,10 @@ module Ransack | |
| 2 2 | 
             
              module Nodes
         | 
| 3 3 | 
             
                class Condition < Node
         | 
| 4 4 | 
             
                  i18n_word :attribute, :predicate, :combinator, :value
         | 
| 5 | 
            -
                  i18n_alias :a => :attribute, :p => :predicate, | 
| 5 | 
            +
                  i18n_alias :a => :attribute, :p => :predicate,
         | 
| 6 | 
            +
                             :m => :combinator, :v => :value
         | 
| 6 7 |  | 
| 7 | 
            -
                   | 
| 8 | 
            +
                  attr_accessor :predicate
         | 
| 8 9 |  | 
| 9 10 | 
             
                  class << self
         | 
| 10 11 | 
             
                    def extract(context, key, values)
         | 
| @@ -13,14 +14,16 @@ module Ransack | |
| 13 14 | 
             
                        combinator = key.match(/_(or|and)_/) ? $1 : nil
         | 
| 14 15 | 
             
                        condition = self.new(context)
         | 
| 15 16 | 
             
                        condition.build(
         | 
| 16 | 
            -
                          : | 
| 17 | 
            -
                          : | 
| 18 | 
            -
                          : | 
| 19 | 
            -
                          : | 
| 17 | 
            +
                          a: attributes,
         | 
| 18 | 
            +
                          p: predicate.name,
         | 
| 19 | 
            +
                          m: combinator,
         | 
| 20 | 
            +
                          v: predicate.wants_array ? Array(values) : [values]
         | 
| 20 21 | 
             
                        )
         | 
| 21 | 
            -
                        # TODO: Figure out what to do with multiple types of attributes, | 
| 22 | 
            +
                        # TODO: Figure out what to do with multiple types of attributes,
         | 
| 23 | 
            +
                        # if anything.
         | 
| 22 24 | 
             
                        # Tempted to go with "garbage in, garbage out" on this one
         | 
| 23 | 
            -
                        predicate.validate(condition.values, condition.default_type) ? | 
| 25 | 
            +
                        predicate.validate(condition.values, condition.default_type) ?
         | 
| 26 | 
            +
                          condition : nil
         | 
| 24 27 | 
             
                      end
         | 
| 25 28 | 
             
                    end
         | 
| 26 29 |  | 
| @@ -37,7 +40,8 @@ module Ransack | |
| 37 40 | 
             
                  end
         | 
| 38 41 |  | 
| 39 42 | 
             
                  def valid?
         | 
| 40 | 
            -
                    attributes.detect(&:valid?) && predicate && valid_arity? && | 
| 43 | 
            +
                    attributes.detect(&:valid?) && predicate && valid_arity? &&
         | 
| 44 | 
            +
                      predicate.validate(values, default_type) && valid_combinator?
         | 
| 41 45 | 
             
                  end
         | 
| 42 46 |  | 
| 43 47 | 
             
                  def valid_arity?
         | 
| @@ -62,7 +66,8 @@ module Ransack | |
| 62 66 | 
             
                        self.attributes << attr if attr.valid?
         | 
| 63 67 | 
             
                      end
         | 
| 64 68 | 
             
                    else
         | 
| 65 | 
            -
                      raise ArgumentError, | 
| 69 | 
            +
                      raise ArgumentError,
         | 
| 70 | 
            +
                        "Invalid argument (#{args.class}) supplied to attributes="
         | 
| 66 71 | 
             
                    end
         | 
| 67 72 | 
             
                  end
         | 
| 68 73 | 
             
                  alias :a= :attributes=
         | 
| @@ -85,7 +90,8 @@ module Ransack | |
| 85 90 | 
             
                        self.values << val
         | 
| 86 91 | 
             
                      end
         | 
| 87 92 | 
             
                    else
         | 
| 88 | 
            -
                      raise ArgumentError, | 
| 93 | 
            +
                      raise ArgumentError,
         | 
| 94 | 
            +
                        "Invalid argument (#{args.class}) supplied to values="
         | 
| 89 95 | 
             
                    end
         | 
| 90 96 | 
             
                  end
         | 
| 91 97 | 
             
                  alias :v= :values=
         | 
| @@ -95,7 +101,7 @@ module Ransack | |
| 95 101 | 
             
                  end
         | 
| 96 102 |  | 
| 97 103 | 
             
                  def combinator=(val)
         | 
| 98 | 
            -
                    @combinator = ['and', 'or'].detect {|v| v == val.to_s} || nil
         | 
| 104 | 
            +
                    @combinator = ['and', 'or'].detect { |v| v == val.to_s } || nil
         | 
| 99 105 | 
             
                  end
         | 
| 100 106 | 
             
                  alias :m= :combinator=
         | 
| 101 107 | 
             
                  alias :m :combinator
         | 
| @@ -113,7 +119,9 @@ module Ransack | |
| 113 119 | 
             
                  end
         | 
| 114 120 |  | 
| 115 121 | 
             
                  def value
         | 
| 116 | 
            -
                    predicate.wants_array ? | 
| 122 | 
            +
                    predicate.wants_array ?
         | 
| 123 | 
            +
                      values.map { |v| v.cast(default_type) } :
         | 
| 124 | 
            +
                      values.first.cast(default_type)
         | 
| 117 125 | 
             
                  end
         | 
| 118 126 |  | 
| 119 127 | 
             
                  def build(params)
         | 
| @@ -131,7 +139,8 @@ module Ransack | |
| 131 139 | 
             
                  end
         | 
| 132 140 |  | 
| 133 141 | 
             
                  def key
         | 
| 134 | 
            -
                    @key ||= attributes.map(&:name).join("_#{combinator}_") + | 
| 142 | 
            +
                    @key ||= attributes.map(&:name).join("_#{combinator}_") +
         | 
| 143 | 
            +
                      "_#{predicate.name}"
         | 
| 135 144 | 
             
                  end
         | 
| 136 145 |  | 
| 137 146 | 
             
                  def eql?(other)
         | 
| @@ -152,11 +161,6 @@ module Ransack | |
| 152 161 | 
             
                  end
         | 
| 153 162 | 
             
                  alias :p= :predicate_name=
         | 
| 154 163 |  | 
| 155 | 
            -
                  def predicate=(predicate)
         | 
| 156 | 
            -
                    @predicate = predicate
         | 
| 157 | 
            -
                    predicate
         | 
| 158 | 
            -
                  end
         | 
| 159 | 
            -
             | 
| 160 164 | 
             
                  def predicate_name
         | 
| 161 165 | 
             
                    predicate.name if predicate
         | 
| 162 166 | 
             
                  end
         | 
| @@ -164,7 +168,9 @@ module Ransack | |
| 164 168 |  | 
| 165 169 | 
             
                  def arel_predicate
         | 
| 166 170 | 
             
                    predicates = attributes.map do |attr|
         | 
| 167 | 
            -
                      attr.attr.send( | 
| 171 | 
            +
                      attr.attr.send(
         | 
| 172 | 
            +
                        predicate.arel_predicate, formatted_values_for_attribute(attr)
         | 
| 173 | 
            +
                        )
         | 
| 168 174 | 
             
                    end
         | 
| 169 175 |  | 
| 170 176 | 
             
                    if predicates.size > 1
         | 
| @@ -180,16 +186,17 @@ module Ransack | |
| 180 186 | 
             
                  end
         | 
| 181 187 |  | 
| 182 188 | 
             
                  def validated_values
         | 
| 183 | 
            -
                    values.select {|v| predicate.validator.call(v.value)}
         | 
| 189 | 
            +
                    values.select { |v| predicate.validator.call(v.value) }
         | 
| 184 190 | 
             
                  end
         | 
| 185 191 |  | 
| 186 192 | 
             
                  def casted_values_for_attribute(attr)
         | 
| 187 | 
            -
                    validated_values.map {|v| v.cast(predicate.type || attr.type)}
         | 
| 193 | 
            +
                    validated_values.map { |v| v.cast(predicate.type || attr.type) }
         | 
| 188 194 | 
             
                  end
         | 
| 189 195 |  | 
| 190 196 | 
             
                  def formatted_values_for_attribute(attr)
         | 
| 191 197 | 
             
                    formatted = casted_values_for_attribute(attr).map do |val|
         | 
| 192 | 
            -
                      val = attr.ransacker.formatter.call(val) if | 
| 198 | 
            +
                      val = attr.ransacker.formatter.call(val) if
         | 
| 199 | 
            +
                        attr.ransacker && attr.ransacker.formatter
         | 
| 193 200 | 
             
                      val = predicate.format(val)
         | 
| 194 201 | 
             
                      val
         | 
| 195 202 | 
             
                    end
         | 
| @@ -201,9 +208,15 @@ module Ransack | |
| 201 208 | 
             
                  end
         | 
| 202 209 |  | 
| 203 210 | 
             
                  def inspect
         | 
| 204 | 
            -
                    data = | 
| 205 | 
            -
                       | 
| 206 | 
            -
             | 
| 211 | 
            +
                    data = [
         | 
| 212 | 
            +
                      ['attributes', a.try(:map, &:name)],
         | 
| 213 | 
            +
                      ['predicate', p],
         | 
| 214 | 
            +
                      ['combinator', m],
         | 
| 215 | 
            +
                      ['values', v.try(:map, &:value)]
         | 
| 216 | 
            +
                    ]
         | 
| 217 | 
            +
                    .reject { |e| e[1].blank? }
         | 
| 218 | 
            +
                    .map { |v| "#{v[0]}: #{v[1]}" }
         | 
| 219 | 
            +
                    .join(', ')
         | 
| 207 220 | 
             
                    "Condition <#{data}>"
         | 
| 208 221 | 
             
                  end
         | 
| 209 222 |  | 
| @@ -216,4 +229,4 @@ module Ransack | |
| 216 229 |  | 
| 217 230 | 
             
                end
         | 
| 218 231 | 
             
              end
         | 
| 219 | 
            -
            end
         | 
| 232 | 
            +
            end
         |