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 to incorporate into the 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) ⇒ Class
Sets the model class that the search targets.
-
.order(name, columns = nil, default: false, **options) ⇒ void
Defines an order that the search can apply to its results.
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
ModelSearch
instance. -
#order_directions ⇒ ActiveSupport::HashWithIndifferentAccess
Returns a
HashWithIndifferentAccess
with 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 model 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 model search object with
criteria_values
merged on top of the subject search object’s criteria values. -
#without(*criteria_names) ⇒ TalentScout::ModelSearch
Builds a new model 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(names, type = :string, **attribute_options) {|value| ... } ⇒ void .criteria(names, type = :string, **attribute_options) ⇒ void .criteria(names, choices: , **attribute_options) {|value| ... } ⇒ void .criteria(names, choices: , **attribute_options) ⇒ void
This method returns an undefined value.
Defines criteria to incorporate into the search. Each criteria corresponds to an attribute on the search object that can be used when building a search form.
Each attribute has a type, just as Active Model attributes do, and values passed into the search object are typecasted before criteria are evaluated. Supported types are the same as Active Model (e.g. :string
, :boolean
, :integer
, etc), with the addition of a :void
type. A :void
type is just like a :boolean
type, except that the criteria is not evaluated when the typecasted value is falsey.
Alternatively, instead of a type, an Array or Hash of choices
can be specified, and the criteria will be evaluated only if the passed-in value matches one of the choices.
Active Model attribute_options
can also be specified. Most notably, the :default
option provides the criteria a default value to operate on.
Each criteria can specify a block which recieves its corresponding typecasted value as an argument. If the corresponding value is not set on the search object, and no default value is defined, the criteria will not be evaluated. Like an Active Record scope
block, a criteria block is evaluated in the context of an ActiveRecord::Relation
, and should return an ActiveRecord::Relation
. A criteria block may also return nil, in which case the criteria will be skipped. If no criteria block is specified, the criteria will be evaluated as a where
clause using the criteria name and typecasted value.
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.
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 274 |
# File 'lib/talent_scout/model_search.rb', line 244 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.
76 77 78 79 |
# File 'lib/talent_scout/model_search.rb', line 76 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 name as the search class, minus the “Search” suffix. The model class can also be set with model_class=. If the model class has not been set, and the default class does not exist, a NameError
will be raised.
32 33 34 35 |
# File 'lib/talent_scout/model_search.rb', line 32 def self.model_class @model_class ||= self.superclass == ModelSearch ? self.name.chomp("Search").constantize : self.superclass.model_class end |
.model_class=(model_class) ⇒ Class
Sets the model class that the search targets. See model_class.
41 42 43 |
# File 'lib/talent_scout/model_search.rb', line 41 def self.model_class=(model_class) @model_class = model_class end |
.order(name, columns = nil, default: false, **options) ⇒ void
This method returns an undefined value.
Defines an order that the search can apply to its results. Only one order can be applied at a time, but an order can be defined over multiple columns. If no columns are specified, the order’s name
is taken as its column.
Each order can be applied in an ascending or descending direction by appending a corresponding 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.
Order direction affects all columns of an order defintion, unless a column explicitly specifies “ASC” or “DESC”, in which case that column will stay fixed in its specified direction.
To apply an order to the search results by default, use the :default
option in the order definition. (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.
An Enumerator is returned if no block is given.
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 model 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 model 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 model 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 |