Class: TalentScout::ModelSearch
- Inherits:
-
Object
- Object
- TalentScout::ModelSearch
- Extended by:
- ActiveModel::Translation
- Includes:
- ActiveModel::Attributes, ActiveModel::Model, ActiveRecord::AttributeAssignment, ActiveRecord::AttributeMethods::BeforeTypeCast
- Defined in:
- lib/talent_scout/model_search.rb
Class Method Summary collapse
-
.criteria(names, type = :string, choices: nil, **attribute_options, &block) ⇒ void
Defines criteria that can be incorporated into a search.
-
.default_scope(&block) ⇒ void
Sets the default scope of the search.
-
.model_class ⇒ Class
Returns the model class that the search targets.
-
.model_class=(model_class) ⇒ model_class
Sets the model class that the search targets.
-
.order(name, columns = [name], **options) ⇒ void
Defines a result order that can be incorporated into a search.
Instance Method Summary collapse
-
#each_choice(criteria_name, &block) ⇒ Object
Iterates over a specified ModelSearch.criteria‘s defined choices.
-
#initialize(params = {}) ⇒ ModelSearch
constructor
Initializes a
ModelSearchinstance. -
#order_directions ⇒ ActiveSupport::HashWithIndifferentAccess
Returns a
HashWithIndifferentAccesswith a key for each defined ModelSearch.order. -
#results ⇒ ActiveRecord::Relation
Applies the ModelSearch.default_scope, search ModelSearch.criteria with set or default attribute values, and the set or default ModelSearch.order to the ModelSearch.model_class.
-
#toggle_order(order_name, direction = nil) ⇒ TalentScout::ModelSearch
Builds a new search object with the specified order applied on top of the subject search object’s criteria values.
-
#with(criteria_values) ⇒ TalentScout::ModelSearch
Builds a new search object with
criteria_valuesmerged on top of the subject search object’s criteria values. -
#without(*criteria_names) ⇒ TalentScout::ModelSearch
Builds a new search object with the subject search object’s criteria values, excluding values specified by
criteria_names.
Constructor Details
#initialize(params = {}) ⇒ ModelSearch
Initializes a ModelSearch instance. Assigns values from params to corresponding criteria attributes.
If params is a ActionController::Parameters, blank values are ignored. This behavior prevents empty search form fields from affecting search results.
382 383 384 385 386 387 388 389 390 |
# File 'lib/talent_scout/model_search.rb', line 382 def initialize(params = {}) # HACK initialize ActiveRecord state required by ActiveRecord::AttributeMethods::BeforeTypeCast @transaction_state ||= nil if params.is_a?(ActionController::Parameters) params = params.permit(self.class.attribute_types.keys).reject!{|key, value| value.blank? } end super(params) end |
Class Method Details
.criteria(name, type = :string, **attribute_options) ⇒ void .criteria(name, type = :string, **attribute_options) {|value| ... } ⇒ void .criteria(name, choices:, **attribute_options) ⇒ void .criteria(name, choices:, **attribute_options) {|value| ... } ⇒ void
This method returns an undefined value.
Defines criteria that can be incorporated into a search. The criteria will have a corresponding attribute on the search object that can be used when building a search form. The type of the attribute can be specified, just as with Active Model attributes, and the attribute value will be typecasted before the criteria is incorporated. Supported types are the same as Active Model (e.g. :string, :boolean, :integer, etc). The default type is :string. An additional :void type is also supported, which typecasts like the :boolean type, but prevents the criteria from being incorporated if the typecasted value is falsey.
Alternatively, instead of a type, an Array or Hash of choices can be specified, and the criteria will be incorporated only if the attribute value matches one of the choices.
Active Model attribute_options can also be specified. Most notably, the :default option can specify a default value for the criteria to operate on. If a default value is not specified, the criteria will be incorporated only if the attribute value is set.
If no block is given, the criteria will be incorporated as a where clause using the criteria name and typecasted attribute value.
If a block is given, it will be passed the typecasted attribute value, and it will be evaluated in the context of an ActiveRecord::Relation, like Active Record scope blocks are. The block should return an ActiveRecord::Relation. The block may also return nil, in which case the criteria will not be incorporated.
As a convenient shorthand, Active Record scopes which have been defined on the model_class can be used directly as criteria blocks by passing the scope’s name as a symbol-to-proc in place of the criteria block.
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/talent_scout/model_search.rb', line 243 def self.criteria(names, type = :string, choices: nil, **, &block) if choices if type != :string raise ArgumentError, "Option :choices cannot be used with type #{type}" end type = ChoiceType.new(choices) elsif type == :void type = VoidType.new elsif type.is_a?(Symbol) # HACK force ActiveRecord::Type.lookup because datetime types # from ActiveModel::Type.lookup don't support multi-parameter # attribute assignment type = ActiveRecord::Type.lookup(type) end crit = Criteria.new(names, !type.is_a?(VoidType), &block) criteria_list << crit crit.names.each do |name| attribute name, type, ** # HACK FormBuilder#select uses normal attribute readers instead # of `*_before_type_cast` attribute readers. This breaks value # auto-selection for types where the two are appreciably # different, e.g. ChoiceType with hash mapping. Work around by # aliasing relevant attribute readers to `*_before_type_cast`. if type.is_a?(ChoiceType) alias_method name, "#{name}_before_type_cast" end end end |
.default_scope(&block) ⇒ void
This method returns an undefined value.
Sets the default scope of the search. Like Active Record’s default_scope, the scope here is specified as a block which is evaluated in the context of the model_class. Also like Active Record, multiple calls of this method will append to the default scope.
75 76 77 78 |
# File 'lib/talent_scout/model_search.rb', line 75 def self.default_scope(&block) i = criteria_list.index{|crit| !crit.names.empty? } || -1 criteria_list.insert(i, Criteria.new([], true, &block)) end |
.model_class ⇒ Class
Returns the model class that the search targets. Defaults to a class with same name as the search class, minus the “Search” suffix. The class can be set with model_class=. If the class has not been set and the default class does not exist, a NameError will be raised.
31 32 33 34 |
# File 'lib/talent_scout/model_search.rb', line 31 def self.model_class @model_class ||= self.superclass == ModelSearch ? self.name.chomp("Search").constantize : self.superclass.model_class end |
.model_class=(model_class) ⇒ model_class
Sets the model class that the search targets. See model_class.
40 41 42 |
# File 'lib/talent_scout/model_search.rb', line 40 def self.model_class=(model_class) @model_class = model_class end |
.order(name, columns = [name], **options) ⇒ void
Defines a result order that can be incorporated into a search. Only one order can be incorporated at a time, but an order can comprise multiple columns. If no columns are specified, the order’s name is taken as its column.
An order can be incorporated in either ascending or descending direction by appending an appropriate suffix to the order value. By default, these suffixes are “.asc” and “.desc”, but they can be overridden in the order definition using the :asc_suffix and :desc_suffix options, respectively.
The order direction will affect all columns of the order, unless a column explicitly specifies “ASC” or “DESC”, in which case that column will stay fixed in its specified direction.
To designate the order as the default result order, use the :default option. (Note that only one order can be designated as the default order.)
360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/talent_scout/model_search.rb', line 360 def self.order(name, columns = nil, default: false, **) definition = OrderDefinition.new(name, columns, **) if !attribute_types.fetch("order", nil).equal?(order_type) || default = default ? { default: definition.choice_for_direction(default) } : {} criteria_list.reject!{|crit| crit.names == ["order"] } criteria "order", order_type, **, &:order end order_type.add_definition(definition) end |
Instance Method Details
#each_choice(criteria_name) {|choice| ... } ⇒ void #each_choice(criteria_name) {|choice, chosen| ... } ⇒ void #each_choice(criteria_name) ⇒ Enumerator
Iterates over a specified criteria‘s defined choices. If the given block accepts a 2nd argument, a boolean will be passed indicating whether that choice is currently assigned to the specified criteria attribute.
If no block is given, an Enumerator is returned instead.
563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
# File 'lib/talent_scout/model_search.rb', line 563 def each_choice(criteria_name, &block) criteria_name = criteria_name.to_s type = self.class.attribute_types.fetch(criteria_name, nil) unless type.is_a?(ChoiceType) raise ArgumentError, "`#{criteria_name}` is not a criteria with choices" end return to_enum(:each_choice, criteria_name) unless block value_after_cast = attribute_set[criteria_name].value type.mapping.each do |choice, value| chosen = value_after_cast.equal?(value) block.arity >= 2 ? block.call(choice, chosen) : block.call(choice) end end |
#order_directions ⇒ ActiveSupport::HashWithIndifferentAccess
Returns a HashWithIndifferentAccess with a key for each defined order. Each key’s associated value indicates that order’s currently applied direction – :asc, :desc, or nil if the order is not applied. Note that only one order can be applied at a time, so, at most, one value in the Hash will be non-nil.
596 597 598 599 600 601 602 |
# File 'lib/talent_scout/model_search.rb', line 596 def order_directions @order_directions ||= begin order_after_cast = attribute_set.fetch("order", nil).try(&:value) self.class.order_type.definitions.transform_values{ nil }. merge!(self.class.order_type.obverse_mapping[order_after_cast] || {}) end.freeze end |
#results ⇒ ActiveRecord::Relation
Applies the default_scope, search criteria with set or default attribute values, and the set or default order to the model_class. Returns an ActiveRecord::Relation, allowing further scopes, such as pagination, to be applied post-hoc.
413 414 415 416 417 |
# File 'lib/talent_scout/model_search.rb', line 413 def results self.class.criteria_list.reduce(self.class.model_class.all) do |scope, crit| crit.apply(scope, attribute_set) end end |
#toggle_order(order_name, direction = nil) ⇒ TalentScout::ModelSearch
Builds a new search object with the specified order applied on top of the subject search object’s criteria values. If the subject search object already has the specified order applied, the order’s direction will be toggled from :asc to :desc or from :desc to :asc. Otherwise, the specified order will be applied with an :asc direction, overriding any previously applied order.
If direction is explicitly specified, that direction will be applied regardless of previously applied direction.
Does not modify the subject search object.
503 504 505 506 507 508 |
# File 'lib/talent_scout/model_search.rb', line 503 def toggle_order(order_name, direction = nil) definition = self.class.order_type.definitions[order_name] raise ArgumentError, "`#{order_name}` is not a valid order" unless definition direction ||= order_directions[order_name] == :asc ? :desc : :asc with(order: definition.choice_for_direction(direction)) end |
#with(criteria_values) ⇒ TalentScout::ModelSearch
Builds a new search object with criteria_values merged on top of the subject search object’s criteria values.
Does not modify the subject search object.
440 441 442 |
# File 'lib/talent_scout/model_search.rb', line 440 def with(criteria_values) self.class.new(attributes.merge!(criteria_values.stringify_keys)) end |
#without(*criteria_names) ⇒ TalentScout::ModelSearch
Builds a new search object with the subject search object’s criteria values, excluding values specified by criteria_names. Default criteria values will still be applied.
Does not modify the subject search object.
466 467 468 469 470 471 472 |
# File 'lib/talent_scout/model_search.rb', line 466 def without(*criteria_names) criteria_names.map!(&:to_s) criteria_names.each do |name| raise ActiveModel::UnknownAttributeError.new(self, name) if !attribute_set.key?(name) end self.class.new(attributes.except!(*criteria_names)) end |