module CushionDefaults::ClassMethods
A series of class methods to be plopped into any class that includes CushionDefaults
.
Attributes
Reader for the @defaults@ @DefaultsHash@.
Reader for the @defaults@ @DefaultsHash@.
Public Instance Methods
Identical to {#cushion_reader} with one important exception: after determining the default value, @bang_reader@ goes on to crystallize this default value for the instance.
This is especially useful with proc cushions, as the below examples make clear.
Note that this method is equivalent to calling @instance.crystallize_default(:sym)@.
Note also, finally, that bang_readers can exist and function even if no {#cushion_writer}s are defined.
@param syms [*#to_sym] instance variables that should have @bang_readers@ @see CushionDefaults::Configuration#bang_things_up
Configuration#bang_things_up
@see CushionDefaults::CushionDefaults#crystalize_default CushionDefaults#crystalize_default
@example Simple Example #1: Flowers for cushion_reader
class Flower include CushionDefaults self.defaults[:he_loves_me] = proc { (Time.now.sec % 2).zero? } cushion :he_loves_me end daisy = Flower.new daisy.he_loves_me # true # slight delay... daisy.he_loves_me # false # slight delay... daisy.he_loves_me! # true 5.times { daisy.he_loves_me } # [true, true, true, true, true]
@example Simple Example #2: Wake Up Little Susie
class Dreamer include CushionDefaults self.defaults[:time_awoken] = proc { Time.now } cushion :time_awoken end little_susie = Dreamer.new little_susie.time_awoken # returns Time.now, but doesn't set @time_awoken little_susie.time_awoken! # sets @time_awoken to Time.now little_susie.time_awoken # returns the @time_awoken set in the line above little_susie.time_awoken! # returns @time_awoken and does not set it to a new value
# File lib/cushion_defaults/class_methods.rb, line 227 def bang_reader(*syms) syms.each do |sym| sym = sym.to_sym bang_sym = "#{sym}!".to_sym if self_or_parent_instance_method?(bang_sym) CushionDefaults.log("#{self} or a parent class already has a bang method #{bang_sym}", :warn) end define_method(bang_sym) do crystallize_default(sym) end CushionDefaults.log("bang_reader #{bang_sym} established for #{self}") end end
Sets up both {#cushion_reader}s and {#cushion_writer}s for @syms@. Will set up {#bang_reader}s if {CushionDefaults::Configuration#bang_things_up Configuration#bang_things_up
} is true. @param syms [*#to_sym] Those instance variables that should have {#cushion_reader}s and {#cushion_writer}s.
# File lib/cushion_defaults/class_methods.rb, line 380 def cushion(*syms) cushion_reader(*syms) cushion_writer(*syms) end
Defines {#cushion_reader}s and {#cushion_writer}s for all of this class’ @defaults@.
Only defines @cushion_readers@ and @cushion_writers@ for this class defaults. All other getters are assumed to have been defined further up the class tree.
Thus, if class @A@ defines the default @var1@, and class @B@ defines the default @var2@, calling this method within class @B@ will only generate a getter for @var2@.
See also:
# File lib/cushion_defaults/class_methods.rb, line 408 def cushion_defaults cushion *defaults.keys CushionDefaults.log("cushions established for #{self}'s defaults': #{defaults.keys.join(', ')}", :info) end
Sets up a cushion_reader
for each :sym in @syms@.
Each reader method checks if its instance variable (:sym) is defined. If it is, it returns that. If not, it returns the default value.
The only exception to this rule is when we have pushy defaults. If a default is pushy, we return it regardless of what this object’s instance variable may or may not be.
Note that if the default responds to :call (a proc, e.g.), then the default is instead called with the instance variable and the symbolic representation of the default requested. This allows for proc cushions.
Will set up {#bang_reader}s if {CushionDefaults::Configuration#bang_things_up Configuration#bang_things_up
} is true.
The readers are named according to the same format as @attr_reader@. @param syms [*#to_sym] instance variables that should have @cushion_readers@ @see Configuration#update_readers
# File lib/cushion_defaults/class_methods.rb, line 106 def cushion_reader(*syms) # send it with force==true syms.each { |sym| _update_cushion_reader(sym.to_sym, true) } bang_reader *syms if CushionDefaults.conf.bang_things_up end
Defines {#cushion_reader}s for all of this class’ @defaults@.
Only defines @cushion_readers@ for defaults specified for this class. All other readers are assumed to have been defined further up the class tree.
Thus, if class @A@ defines the default @var1@, and class @B@ defines the default @var2@, calling this method within class @B@ will only generate a reader for @var2@.
# File lib/cushion_defaults/class_methods.rb, line 393 def cushion_readers_for_defaults cushion_reader *defaults.keys end
Sets up a cushion_writer
for each :sym in @syms@.
Each writer method carries out one of several checks.
First, it sees if the default is nil or an empty string, and if we care whether this is the case. If {Configuration#ignore_attempts_to_set_nil} is true, we will not set the instance variable to nil; and correspondingly, if {Configuration#blank_str_is_nil} is true, we will not set the instance variable to ”. This failure will be logged at the debug level unless {Configuration#whiny_ignores}, in which case it will be logged at warn.
Assuming the above checks pass, we next check whether the key we’re interested in is pushy. If it is, it warns us that the new value will have no effect since it will be overridden by a pushy default.
Finally, assuming it was not flagged as nilish, the variable is set to the new value.
The writers are named according to the same format as @attr_writer@. @param syms [*#to_sym] instance variables that should have @cushion_writers@ @see Configuration#update_writers
@see Configuration#ignore_attempts_to_set_nil
@see Configuration#blank_str_is_nil
@see Configuration#whiny_ignores
# File lib/cushion_defaults/class_methods.rb, line 261 def cushion_writer(*syms) syms.each do |sym| method_name = "#{sym}=".to_sym if self_or_parent_instance_method?(method_name) CushionDefaults.log("#{self} or a parent class already has what looks like a setter method for #{sym}", :warn) end instance_variable_sym = "@#{sym}".to_sym defaults_cache = @defaults define_method(method_name) do |y| if CushionDefaults.nilish? y if CushionDefaults.conf.whiny_ignores CushionDefaults.log("You are attempting to set a nilish value for #{sym}. This will not be recorded, and any value set will be deleted.", :warn) end else if CushionDefaults.conf.we_have_a_pushy? && defaults_cache.pushy?(sym) CushionDefaults.log("You are setting a value for #{sym}, but this is a pushy default and this value will not be returned by any cushion_readers.", :warn) end instance_variable_set(instance_variable_sym, y) end end CushionDefaults.log("cushion_writer #{sym}= established for #{self}") end end
In addition to preventing the @default@ from being set to a new value (see {#freeze_default}), also freezes the value to which @default@ is currently set.
Does not throw an error when you attempt to freeze an already-frozen default, but it does log at warning level.
Equivalent to calling: @freeze_default :sym; defaults.freeze@
@api freeze
# File lib/cushion_defaults/class_methods.rb, line 451 def deep_freeze_default(*syms) syms.each do |sym| sym = sym.to_sym freeze_default sym defaults[sym].freeze end end
Calls {#deep_freeze_default} for all defaults.
@api freeze
# File lib/cushion_defaults/class_methods.rb, line 462 def deep_freeze_defaults deep_freeze_default *defaults.keys end
@!attribute [w] defaults Wipe @defaults and replace it with the keys/vals of @replacement_hash@.
If you only want to add one or more defaults, then write instead self.defaults += {new_key: val}.
Note that all keys are coerced into @Symbols@.
# File lib/cushion_defaults/class_methods.rb, line 16 def defaults=(replacement_hash) # Need to copy over keys/vals to ensure @defaults remains a DefaultsHash and retains identity CushionDefaults.log("Old defaults deleted for #{self}:#{defaults.empty? ? ' [none]' : @defaults.reduce(''){|s,(k,v)| s+"\n\t\t#{k}: #{v}"}}", :info) unless @defaults.empty? @defaults.replace(replacement_hash) CushionDefaults.log("New defaults added for #{self}:#{defaults.empty? ? ' [none]' : @defaults.reduce(''){|s,(k,v)| s+"\n\t\t#{k}: #{v}"}}", :info) @defaults.keys.each do |key| unless key.is_a? Symbol @defaults[key.to_sym] = @defaults[key].delete! end end end
Load in the defaults for this class from a YAML file. If @file_name@ is specified, this YAML file is loaded. Otherwise, {Configuration#yaml_file_for} is evaluated for the current class. By default, the yaml file for @Klass@ is expected to be at @config/cushion_defaults/klass.yaml@. @param file_name [String] name of the YAML file to be loaded in for this class @see Configuration#auto_load_from_yaml
@see Configuration#whiny_yaml
# File lib/cushion_defaults/class_methods.rb, line 44 def defaults_from_yaml(file_name = nil) if file_name yaml_path = "#{CushionDefaults.conf.yaml_source_full_path}#{file_name}.yaml" else yaml_path = CushionDefaults.conf.yaml_file_for(self) end yaml = YAML::load(File.open(yaml_path)) rescue {} if yaml.empty? CushionDefaults.log("No YAML configuration for class #{self} found at #{yaml_path}", CushionDefaults.conf.whiny_yaml ? :warn : :debug) end initialize_defaults_hash log_str = "New defaults added for #{self}:#{' [none]' if yaml.empty?}" # If automatic readers and writers are enabled, this will set them up as a consequence. yaml.each do |key, val| log_str << "\n\t\t#{key}: #{val}" @defaults[key.to_sym] = val end CushionDefaults.log(log_str, :info) end
Prevents a default from being set to a new value. Freezing a default is permanent in the life of the program.
Note that while a frozen default is guaranteed to maintain its identity, its attributes can still be modified (e.g., by bang! methods). To prevent any modification to a default value, call {#deep_freeze_default}.
Does not throw an error when you attempt to freeze an already-frozen default, but it does log at warning level.
@note A frozen default can still be overridden lower in the class hierarchy. @see thaw_default(sym)
@api freeze
# File lib/cushion_defaults/class_methods.rb, line 426 def freeze_default(*syms) syms.each do |sym| sym = sym.to_sym unless defaults.freeze_default! sym # It returns nil if the key was already present in the Set CushionDefaults.log("Cannot freeze #{sym}: it was already frozen for #{self}", :warn) end end end
Calls {#freeze_default} for all defaults.
@api freeze
# File lib/cushion_defaults/class_methods.rb, line 439 def freeze_defaults freeze_default *defaults.keys end
Ensure that if class @Klass@ includes @CushionDefaults@, then any class that subclasses @Klass@ will include it as well.
Called automatically whenever any class that includes @CushionDefaults@ is inherited. @api private
# File lib/cushion_defaults/class_methods.rb, line 557 def inherited(inheritor) inheritor.send :include, CushionDefaults @descendents << inheritor super inheritor end
Either set up or wipe @defaults. Should not usually be called directly. @api private
# File lib/cushion_defaults/class_methods.rb, line 69 def initialize_defaults_hash if @defaults # We need to maintain the identity of the hash, as child classes may have stored a reference to it @defaults.clear else @defaults = DefaultsHash.new(self) end end
Declare each sym to be polite. A polite default operates as normal. This is useful to override an ancestor class’ pushy declaration.
This will automatically create {#cushion_reader}‘s for all variables made polite.
# File lib/cushion_defaults/class_methods.rb, line 510 def make_polite(*syms) syms.each do |sym| @defaults.not_pushy!(sym) _update_cushion_reader(sym, true) end end
Declare each sym to be pushy. Pushy defaults will be returned by {#cushion_reader}s regardless of what the instance variables are set to.
This will automatically create {#cushion_reader}‘s for all variables made pushy.
# File lib/cushion_defaults/class_methods.rb, line 499 def make_pushy(*syms) syms.each do |sym| @defaults.pushy!(sym) _update_cushion_reader(sym, true) end end
Undefines any bang method for sym in this class (if present). Does not affect ancestor classes. @note This method will delete any method of the form @sym!@, not just bang readers. @param sym [#to_sym] instance variable to delete bang method for @deprecated
# File lib/cushion_defaults/class_methods.rb, line 315 def remove_bang(sym) sym = "#{sym}!".to_sym if self_has_method?(sym) undef_method(sym) @have_bang_readers.delete(sym) CushionDefaults.log("bang reader #{sym} removed from #{self}", :info) end end
Undefines any bang method for sym in this class (if present). Does not affect ancestor classes. @note This method will delete any method of the form @sym!@, not just bang readers. @param sym [#to_sym] instance variable to delete bang method for
# File lib/cushion_defaults/class_methods.rb, line 327 def remove_cushion_bang(sym) sym = "#{sym}!".to_sym if @have_bang_readers.delete?(sym) && self_has_method?(sym) undef_method(sym) CushionDefaults.log("bang reader #{sym} removed from #{self}", :info) end end
Undefines the cushion_reader
for sym in this class (if present). Does not affect ancestor classes. @param sym [#to_sym] instance variable to delete cushion_reader
for
# File lib/cushion_defaults/class_methods.rb, line 303 def remove_cushion_reader(sym) sym = sym.to_sym if @have_cushion_readers.delete?(sym) && self_has_method?(sym) undef_method(sym) CushionDefaults.log("cushion_reader #{sym} removed from #{self}", :info) end end
Undefines any writer for sym in this class (if present). Does not affect ancestor classes. @note This method will delete any method of the format ‘sym=’, not just cushion_writers. @param sym [#to_sym] instance variable to delete writer for
# File lib/cushion_defaults/class_methods.rb, line 351 def remove_cushion_writer(sym) write_sym = "#{sym}=".to_sym if @have_cushion_writers.delete?(write_sym) && self_has_method?(write_sym) undef_method(write_sym) CushionDefaults.log("cushion_writer #{sym}= removed from #{self}", :info) end end
Undefines the reader for sym in this class (if present). Does not affect ancestor classes. @note This method will delete any method of the form @sym@, not just cushion_readers. @param sym [#to_sym] instance variable to delete reader for @deprecated
# File lib/cushion_defaults/class_methods.rb, line 292 def remove_reader(sym) sym = sym.to_sym if self_has_method?(sym) undef_method(sym) @have_cushion_readers.delete(sym) CushionDefaults.log("cushion_reader #{sym} removed from #{self}", :info) end end
Undefines any writer for sym in this class (if present). Does not affect ancestor classes. @note This method will delete any method of the format ‘sym=’, not just cushion_writers. @param sym [#to_sym] instance variable to delete writer for @deprecated
# File lib/cushion_defaults/class_methods.rb, line 339 def remove_writer(sym) write_sym = "#{sym}=".to_sym if self_has_method?(write_sym) undef_method(write_sym) @have_cushion_writers.delete(write_sym) CushionDefaults.log("cushion_writer #{sym}= removed from #{self}", :info) end end
Equivalent to @@defaults = val@ @param key [#to_sym] key for new default. @param val [Object] Default value for @key@.
# File lib/cushion_defaults/class_methods.rb, line 32 def set_default(key, val) @defaults[key.to_sym] = val end
Thaws a frozen default, allowing it to be set to a new value.
Note that if the value of the @default@ is itself frozen (using {#deep_freeze_default} or @Object#freeze@), thaw_default
allows you to set @default@ to a new value but does not and cannot allow you to modify the value to which @default@ points until and unless @default@ is set to a new value.
Does not throw an error when you attempt to thaw a non-frozen default, but it does log at warning level.
@api freeze
# File lib/cushion_defaults/class_methods.rb, line 475 def thaw_default(*syms) syms.each do |sym| sym = sym.to_sym unless defaults.thaw_default! sym # It returns nil if the key was not present in the Set CushionDefaults.log("Cannot thaw #{sym}: it was already thawed for #{self}", :warn) end CushionDefaults.log("After thawing, currently frozen defaults are #{defaults.frozen_defaults.to_a.join(', ')}") end end
Calls {#thaw_default} for all defaults.
@api freeze
# File lib/cushion_defaults/class_methods.rb, line 489 def thaw_defaults thaw_default *defaults.keys end
Protected Instance Methods
Check whether this class includes the instance method denoted by @sym@ @param sym [#to_sym] method in question
# File lib/cushion_defaults/class_methods.rb, line 573 def self_has_method?(sym) instance_methods(false).include?(sym.to_sym) end
Check whether this class or any parent class includes the instance method denoted by @sym@ @param sym [#to_sym] method in question
# File lib/cushion_defaults/class_methods.rb, line 567 def self_or_parent_instance_method?(sym) instance_methods(true).include?(sym.to_sym) end
Private Instance Methods
Called when a variable either becomes polite or may have become polite.
# File lib/cushion_defaults/class_methods.rb, line 540 def _cascade_polite_symbol(sym) # if we get down far enough, it may become pushy again return unless @defaults.not_pushy? sym _reveal_cushion_reader(sym) if self_has_method? "_#{sym}_hidden".to_sym # cascade it down to descendents @descendents.each { |desc| desc._cascade_polite_symbol sym if respond_to? :_cascade_polite_symbol } end
Called when a variable either becomes pushy or may have become pushy.
# File lib/cushion_defaults/class_methods.rb, line 518 def _cascade_pushy_symbol(sym) # if we get down far enough, it may no longer be pushy (we read a polite) # at that point, we can assume accurate state return unless @defaults.pushy? sym if self_has_method? sym if @defaults.pushy_in_parent? sym # if it's pushy in a parent now, then hide the reader # we hide it rather than delete it because we want to be able to restore a manually-created cushion_reader if # this is later made not-polite _hide_cushion_reader sym @have_cushion_readers.delete sym # cascade it down to descendents @descendents.each { |desc| desc._cascade_pushy_symbol sym if respond_to? :_cascade_pushy_symbol } else # if it's pushy in us (has to be, if not pushy in parent) then just update the reader _update_cushion_reader sym # also, if it's pushy in us, we don't need to look at descendents any more, so no need to call end end end
@api private
# File lib/cushion_defaults/class_methods.rb, line 130 def _define_cushion_reader_no_pushy_dynamic(sym) defaults_cache = @defaults instance_variable_sym = "@#{sym}".to_sym define_method(sym) do instance_variable_defined?(instance_variable_sym) ? instance_variable_get(instance_variable_sym) : defaults_cache[sym].call(self, sym) end end
@api private
# File lib/cushion_defaults/class_methods.rb, line 139 def _define_cushion_reader_no_pushy_static(sym) defaults_cache = @defaults instance_variable_sym = "@#{sym}".to_sym define_method(sym) do instance_variable_defined?(instance_variable_sym) ? instance_variable_get(instance_variable_sym) : defaults_cache[sym] end end
@api private
# File lib/cushion_defaults/class_methods.rb, line 113 def _define_cushion_reader_pushy_dynamic(sym) defaults_cache = @defaults define_method(sym) do defaults_cache[sym].call(self, sym) da_default.respond_to?(:call) ? da_default.call(self, sym) : da_default end end
@api private
# File lib/cushion_defaults/class_methods.rb, line 122 def _define_cushion_reader_pushy_static(sym) defaults_cache = @defaults define_method(sym) do defaults_cache[sym] end end
# File lib/cushion_defaults/class_methods.rb, line 359 def _hide_cushion_reader(sym) # Called (1) when a cushion reader exists and (2) the reader needs to be hidden alias_method "_#{sym}_hidden".to_sym, sym remove_method sym end
# File lib/cushion_defaults/class_methods.rb, line 365 def _reveal_cushion_reader(sym) # Called (1) when a previous cushion reader exists but (2) it was hidden due to a parent's sym becoming pushy alias_method sym, "_#{sym}_hidden".to_sym remove_method "_#{sym}_hidden".to_sym # Things might have changed, so we should update the method @have_cushion_readers.add sym _update_cushion_reader sym end
# File lib/cushion_defaults/class_methods.rb, line 78 def _set_up_cushion_method_sets @have_cushion_readers = Set.new @have_bang_readers = Set.new @have_cushion_writers = Set.new @descendents = Set.new end
@param sym [Symbol] the attribute to update the cushion_reader
for @param force [boolean] if true, it will always update the method @api private
# File lib/cushion_defaults/class_methods.rb, line 152 def _update_cushion_reader(sym, force=false) # This will redefine a method each time the default is changed, but at this point that does not seem like a big # enough performance hit to warrant the checks required to avoid it. # force: should we force it to continue? (Basically should only be forced by {#cushion_reader}) # include?: if the reader already exists (i.e., it was previously manually created) then continue # update_readers: if we're updating readers automatically, then continue return unless force || @have_cushion_readers.include?(sym) || CushionDefaults.conf.update_readers # include?: so we only show this warning once if !@have_cushion_readers.include?(sym) && self_or_parent_instance_method?(sym) CushionDefaults.log("#{self} or a parent class already has what looks like an attr_reader for #{sym}", :warn) end # Create one of four cushion_reader methods depending on the state and type of the default value if @defaults.pushy? sym @defaults[sym].respond_to?(:call) ? _define_cushion_reader_pushy_dynamic(sym) : _define_cushion_reader_pushy_static(sym) else default = @defaults[sym] default.respond_to?(:call) ? _define_cushion_reader_no_pushy_dynamic(sym) : _define_cushion_reader_no_pushy_static(sym) end @have_cushion_readers.add sym CushionDefaults.log("cushion_reader #{sym} updated for #{self}") end