Module: HasHelpers::Constant::ClassMethods
- Defined in:
- lib/has_helpers/constant.rb
Instance Attribute Summary collapse
-
#abstract_constant_class ⇒ Object
An abstract constant class is a class that constant classes inherit from but does not have any constants of its own.
-
#grouped ⇒ Object
grouped it is used to filter constants with group_id = nil as default scope.
- #simple ⇒ Object
Instance Method Summary collapse
-
#const_missing(constant_name) ⇒ Array, ActiveRecord
Method to be able to reference a constant via Active Record Handles a GET request.
- #constant_names ⇒ Object
- #define_constant(constant_definition, **attrs) ⇒ Object
-
#define_constant_group(parent, group_name: nil, group_foreign_key: nil, constants: nil, include_parent_group_id: true, &block) ⇒ Object
Defines a single constant group.
-
#define_constants(*constant_definitions, key_prefix: nil, **attrs) ⇒ Object
Returns constant definitions.
- #group_proc(const_defs, parent, block) ⇒ Object
- #is_parent?(klass) ⇒ Boolean
-
#lookup(query = nil, name: query, id: nil, key: nil, case_sensitive: false, **attrs) ⇒ Object
Accepts a query string and returns the matching record, if any.
-
#seed! ⇒ Object
Load the seeds for this constant.
- #set_parent_child_associations ⇒ Object
- #supports_grouping? ⇒ Boolean
Instance Attribute Details
#abstract_constant_class ⇒ Object
An abstract constant class is a class that constant classes inherit from
but does not have any constants of its own. This prevents the ALL constant
from being defined on an ancestor, which can cause problems for its descendants.
304 305 306 |
# File 'lib/has_helpers/constant.rb', line 304 def abstract_constant_class @abstract_constant_class end |
#grouped ⇒ Object
grouped it is used to filter constants with group_id = nil as default scope.
315 316 317 |
# File 'lib/has_helpers/constant.rb', line 315 def grouped @grouped || false end |
#simple ⇒ Object
319 320 321 |
# File 'lib/has_helpers/constant.rb', line 319 def simple @simple || (superclass.respond_to?(:simple) && superclass.simple) end |
Instance Method Details
#const_missing(constant_name) ⇒ Array, ActiveRecord
Method to be able to reference a constant via Active Record Handles a GET request
335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/has_helpers/constant.rb', line 335 def const_missing(constant_name) constant_name = constant_name.to_sym if constant_name == :ALL && !self.abstract_constant_class # Special case for ::ALL const_set(:ALL, __regular_constant_definitions__.map { |definition| __fetch_const_from_definition__(definition) }) elsif constant_name == :ALL_GROUPS && !self.abstract_constant_class # Special case for :ALL_GROUPS const_set(:ALL_GROUPS, __parent_constants__.map { |definition| __fetch_const_from_definition__(definition) }) elsif __constants__.has_key?(constant_name) const_set(constant_name, __constants__[constant_name].load(self)) else super end end |
#constant_names ⇒ Object
455 456 457 |
# File 'lib/has_helpers/constant.rb', line 455 def constant_names __regular_constant_definitions__.map(&:name) end |
#define_constant(constant_definition, **attrs) ⇒ Object
370 371 372 |
# File 'lib/has_helpers/constant.rb', line 370 def define_constant(constant_definition, **attrs) define_constants(constant_definition, **attrs).first end |
#define_constant_group(parent, group_name: nil, group_foreign_key: nil, constants: nil, include_parent_group_id: true, &block) ⇒ Object
Defines a single constant group
Constants in the group can be either actual ruby constants, constant definitions, or strings.
If they're actual ruby constants, then they should go in a block that's passed into either the to_constant method of the parent or the define_constant_group method.
If they're strings or constant definitions, then they should go in the constants parameter.
The group_name parameter is used to set a custom group name. This is only used when the group is persisted in the database. If the group is persisted and no group_name is given, then it defaults to #HasHelpers::Constant::ClassMethods.parentparent.keyparent.key.upcase_GROUP
The group_foreign_key parameter determines whether or not the group association is persisted into the database. If it's nil, then the group exists only in memory, otherwise both the children and the parent will have the column specified by the group_foreign_key set to the parent's id.
The include_parent_group_id parameter is used to set the group_id of the parent (false by default) This behavior sometimes is needed.
Examples
# This shows the different ways to put constants in a group
define_constants('Lapsed', 'Reversed', 'Returned', 'Replaced')
define_constant_group(
'Closed'.to_constant { [LAPSED, REVERSED] }, # Closed is the parent, LAPSED and REVERSE are in the group
group_name: "CLOSED_STATUSES", # CLOSED evaluates to the parent closed status, while CLOSED_STATUSES gives the constants in the group.
group_foreign_key: :group_id, # All constants in the group will have their group_id set to CLOSED.id
constants: ['DECLINED', 'SURRENDERED']
) { [RETURNED, REPLACED] } # RETURNED and REPLACED are in the group
define_constant_group "POLICIES" do
[AMENDMENT_FORM, APPLICATIONS, AWAIT_FUNDING, TENTHIRTYFIVE_FORM]
end
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/has_helpers/constant.rb', line 410 def define_constant_group(parent, group_name: nil, group_foreign_key: nil, constants: nil, include_parent_group_id: true, &block) child_defs = constants ? Array(constants).map(&:to_constant).each { |child| child.group_foreign_key = group_foreign_key } : nil child_defs = define_constants(*child_defs.map(&:to_constant)) if child_defs&.any? group_block = group_proc(child_defs, parent, block) parent_definition = parent.to_constant.tap do |parent_def| parent_def.group_foreign_key = group_foreign_key parent_def.persisted_parent = !!group_foreign_key parent_def.block = group_block if group_block parent_def.include_parent_group_id = include_parent_group_id end if group_foreign_key # When the parent is an actual constant in the database, we assign a separate name for the group # so that parent constant still evaluates to its record. group_name ||= "#{parent.to_s.constant_case}_GROUP" group_definition = group_name.to_constant.tap do |group| group.block = proc do child_constants = group_block.call [__fetch_const_from_definition__(parent_definition), *child_constants].uniq end end if self.reflect_on_association(:children).nil? self.has_many :group_children, class_name: self.name, foreign_key: "#{group_foreign_key}" self.belongs_to :group_parent, class_name: self.name, foreign_key: "#{group_foreign_key}", optional: true end define_constant(group_definition) end define_constant(parent_definition) end |
#define_constants(*constant_definitions, key_prefix: nil, **attrs) ⇒ Object
Returns constant definitions
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/has_helpers/constant.rb', line 349 def define_constants(*constant_definitions, key_prefix: nil, **attrs) constant_definitions.map(&:to_constant).tap do |definitions| definitions.each do |constant_definition| constant_definition.attrs.merge!(attrs) constant_definition.key = "#{ key_prefix.constant_case }_#{ constant_definition.key }" if key_prefix existing_attrs = __constants__[constant_definition.key]&.attrs || {} constant_definition.attrs = existing_attrs.merge!(constant_definition.attrs) __constants__[constant_definition.key] = constant_definition # Register the definition in the look-up hash # This gets a bit complicated, but in short we need a way to prevent Ruby from returning # a constant which is defined in an ancestor module. E.g. if `IMO` is a top-level class # then we don't want `MyConstant::IMO` to return that top-level class. # To stop this undesired behavior the code below sets the constant on the current class # to be a proxy. When that Proxy is evaluated it will set load the constant as expected. remove_const(constant_definition.key) if const_defined?(constant_definition.key, false) const_set(constant_definition.key, Proxy.new(self, constant_definition.key)) end end end |
#group_proc(const_defs, parent, block) ⇒ Object
442 443 444 445 446 447 448 449 450 451 452 453 |
# File 'lib/has_helpers/constant.rb', line 442 def group_proc(const_defs, parent, block) existing_parent_block = parent.to_constant.block proc do grouped_constants = [] grouped_constants << existing_parent_block.call if existing_parent_block grouped_constants << __defs_to_consts__(const_defs) if const_defs grouped_constants << block.call if block grouped_constants.flatten! grouped_constants = grouped_constants.first if grouped_constants.one? && grouped_constants.first.is_a?(::Hash) grouped_constants end end |
#is_parent?(klass) ⇒ Boolean
327 328 329 |
# File 'lib/has_helpers/constant.rb', line 327 def is_parent?(klass) __parent_constants__.any? { |c| c.name === klass.name } end |
#lookup(query = nil, name: query, id: nil, key: nil, case_sensitive: false, **attrs) ⇒ Object
Accepts a query string and returns the matching record, if any. If there is an ambiguity which causes more than one record to be match then an error will be raised. For such cases it may be necessary to override this method and provide a way to disambiguate lookups.
Examples
Lookup by name
FujitaScale.lookup("F1") # =>
# Lookup by an attribute other than name
FujitaScale.lookup(key: :F1) # => <FujitaScale name="F1">
State.lookup(abbreviation: "KS") # => <State name="Kansas" abbreviation="KS">
# Lookup by incorrect name (does not exist)
FujitaScale.lookup("F14") # => nil
# Ambiguous lookup (multiple matches)
Alignment.lookup(type: "Horizontal") # => Error!
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 |
# File 'lib/has_helpers/constant.rb', line 516 def lookup(query = nil, name: query, id: nil, key: nil, case_sensitive: false, **attrs) if name || id || key || attrs.any? # Perform lookup if any criterion is present. Otherwise nil is returned. matches = Enumerator.new do |yielder| already_yielded = false case_converter = case_sensitive ? :itself : :downcase __constants__. lazy. select do |constant_key, definition| (!name || definition.name.send(case_converter) == name.send(case_converter)) && (!key || key.to_s == constant_key.to_s) && attrs.all? { |k, v| definition.attrs(force_eval: true)[k] == v } end. each do |_, definition| raise "Lookup matched more than one record" if already_yielded const = __fetch_const_from_definition__(definition) # We can't lookup by id until after loading constants if !id || (!const.is_a?(Array) && const.id.to_s == id.to_s) yielder << const already_yielded = true # Break early if we find a matching record with an id. # An id will be unique so we don't care about raising # an error when there are more than one matched record. break if id end end end matches.to_a.first end end |
#seed! ⇒ Object
Load the seeds for this constant.
Note, if the current constant depends on other constants, then those should be loaded first.
Do so by overriding the .seed! method, loading the dependencies, and then calling super.
class Registration
def self.seed!
::Exam.seed! # Load Exams before Registrations.
super
end
468 469 470 471 472 473 |
# File 'lib/has_helpers/constant.rb', line 468 def seed! self.grouped = false __regular_constant_definitions__.each { |definition| definition.seed!(self) } set_parent_child_associations nil # Returns this rather than the enumerator from the previous line. end |
#set_parent_child_associations ⇒ Object
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/has_helpers/constant.rb', line 475 def set_parent_child_associations __parent_constants__.each do |parent_definition| parent_instance = parent_definition.load(self) parent_instance.reload if parent_definition.include_parent_group_id parent_definition.attrs["#{parent_definition.group_foreign_key}"] = parent_instance.id parent_instance.send("#{parent_definition.group_foreign_key}=", parent_instance.id) parent_instance.save! children = parent_definition.evaluate children.each do |child_instance| foreign_key = parent_definition.group_foreign_key # Simple constants are created w/o fetching from the db. # Since we're saving the constant, we need to load from the database so attributes are up-to-date child_instance.reload if simple child_instance.public_send("#{foreign_key}=", parent_instance.id) child_instance.save! child_def = child_instance.definition child_def.attrs["#{foreign_key}"] = parent_instance.id __constants__[child_def.key] = child_def end end end |
#supports_grouping? ⇒ Boolean
323 324 325 |
# File 'lib/has_helpers/constant.rb', line 323 def supports_grouping? !!reflect_on_association(:group_children) end |