Module: ApiResource::Attributes
- Extended by:
- ActiveSupport::Concern
- Includes:
- ActiveModel::AttributeMethods, ActiveModel::Dirty
- Included in:
- Base
- Defined in:
- lib/api_resource/attributes.rb
Defined Under Namespace
Modules: ClassMethods
Instance Method Summary collapse
-
#[](attr_name) ⇒ Object
Reads an attribute and raises MissingAttributeError.
-
#[]=(attr_name, value) ⇒ Object
Write the value to the given attribute.
-
#attribute?(name) ⇒ Boolean
Wrapper for the class method attribute? that returns true if the name parameter is the name of any attribute.
-
#attributes ⇒ HashWithIndifferentAccess
Returns a hash of attribute names as keys and typecasted values as hash values.
-
#attributes=(attrs) ⇒ Hash
Handles mass assignment of attributes, including sanitizing them for mass assignment.
- #clear_changes(*attrs) ⇒ Object
-
#initialize(*args) ⇒ Object
Override for initialize to set up attribute and attributes cache.
- #make_changes_current ⇒ Object
- #method_missing(sym, *args, &block) ⇒ Object
-
#protected_attribute?(name) ⇒ Boolean
Wrapper for the class method protected_attribute?.
-
#public_attribute?(name) ⇒ Boolean
Wrapper for the class method public_attribute?.
-
#read_attribute(attr_name) ⇒ Object
Reads an attribute typecasting if necessary and setting the cache so as to only typecast the one time.
-
#read_attribute_before_type_cast(attr_name) ⇒ Object
Reads the attribute directly out of the attributes hash without applying any typecasting if that attribute is not found.
- #reset_changes ⇒ Object
-
#respond_to?(sym, include_private_methods = false) ⇒ Boolean
Override to respond_to? for finding attribute methods even if they are not defined.
-
#save_with_dirty_tracking(*args) ⇒ Boolean
Override for the save method to update our dirty tracking of attributes.
-
#typecast_attribute_for_read(attr_name, value) ⇒ Object
Returns the typecasted value of an attribute for being read (calls from_api on the typecaster).
-
#typecast_attribute_for_write(attr_name, value) ⇒ Object
Returns the typecasted value of the attribute for being written (calls to_api on the typecaster).
-
#write_attribute(attr_name, value) ⇒ Object
Writes an attribute, first typecasting it to the proper type with typecast_attribute_for_write and then setting it in the attributes hash.
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(sym, *args, &block) ⇒ Object
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
# File 'lib/api_resource/attributes.rb', line 543 def method_missing(sym, *args, &block) sym = sym.to_sym # Maybe the resource definition sucks... if self.class.resource_definition_is_invalid? self.class.reload_resource_definition end # If we don't respond by now... if self.respond_to?(sym) return self.send(sym, *args, &block) elsif @attributes.keys.symbolize_array.include?(sym) # Try returning the attributes from the attributes hash return @attributes[sym] end # Fall back to class method_missing super end |
Instance Method Details
#[](attr_name) ⇒ Object
Reads an attribute and raises MissingAttributeError
452 453 454 455 456 |
# File 'lib/api_resource/attributes.rb', line 452 def [](attr_name) read_attribute(attr_name) do |n| self.missing_attribute(attr_name, caller(0)) end end |
#[]=(attr_name, value) ⇒ Object
Write the value to the given attribute
464 465 466 |
# File 'lib/api_resource/attributes.rb', line 464 def []=(attr_name, value) write_attribute(attr_name, value) end |
#attribute?(name) ⇒ Boolean
Wrapper for the class method attribute? that returns true if the name parameter is the name of any attribute
of an attribute
477 478 479 |
# File 'lib/api_resource/attributes.rb', line 477 def attribute?(name) self.class.attribute?(name) end |
#attributes ⇒ HashWithIndifferentAccess
Returns a hash of attribute names as keys and typecasted values as hash values
411 412 413 414 415 416 417 |
# File 'lib/api_resource/attributes.rb', line 411 def attributes hash = HashWithIndifferentAccess.new self.class.attribute_names.each_with_object(hash) do |name, attrs| attrs[name] = read_attribute(name) end end |
#attributes=(attrs) ⇒ Hash
Handles mass assignment of attributes, including sanitizing them for mass assignment. Which by default does nothing but would if you were to use this in rails 4 or with strong_parameters
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/api_resource/attributes.rb', line 427 def attributes=(attrs) unless attrs.respond_to?(:symbolize_keys) raise ArgumentError, 'You must pass a hash when assigning attributes' end return if attrs.blank? attrs = attrs.symbolize_keys # First deal with sanitizing for mass assignment # this raises an error if attrs violates mass assignment rules attrs = self.sanitize_for_mass_assignment(attrs) attrs.each do |name, value| self._assign_attribute(name, value) end attrs end |
#clear_changes(*attrs) ⇒ Object
568 569 570 571 572 |
# File 'lib/api_resource/attributes.rb', line 568 def clear_changes(*attrs) @previously_changed = {} @changed_attributes = {} true end |
#initialize(*args) ⇒ Object
Override for initialize to set up attribute and attributes cache
ignored in this case
271 272 273 274 275 276 277 |
# File 'lib/api_resource/attributes.rb', line 271 def initialize(*args) @attributes = HashWithIndifferentAccess[ self.class.attribute_names.zip([]) ] @attributes_cache = HashWithIndifferentAccess.new @previously_changed = HashWithIndifferentAccess.new @changed_attributes = HashWithIndifferentAccess.new super() end |
#make_changes_current ⇒ Object
563 564 565 566 |
# File 'lib/api_resource/attributes.rb', line 563 def make_changes_current @previously_changed = self.changes @changed_attributes.clear end |
#protected_attribute?(name) ⇒ Boolean
Wrapper for the class method protected_attribute?
487 488 489 |
# File 'lib/api_resource/attributes.rb', line 487 def protected_attribute?(name) self.class.protected_attribute?(name) end |
#public_attribute?(name) ⇒ Boolean
Wrapper for the class method public_attribute?
497 498 499 |
# File 'lib/api_resource/attributes.rb', line 497 def public_attribute?(name) self.class.public_attribute?(name) end |
#read_attribute(attr_name) ⇒ Object
Reads an attribute typecasting if necessary and setting the cache so as to only typecast the one time. Takes a block which is called if the attribute is not found
is not found
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/api_resource/attributes.rb', line 289 def read_attribute(attr_name) attr_name = attr_name.to_sym @attributes_cache[attr_name] || @attributes_cache.fetch(attr_name) do data = @attributes.fetch(attr_name) do # This statement overrides id to return the primary key # if it is set to something other than :id if attr_name == :id && self.class.primary_key != attr_name return read_attribute(self.class.primary_key) end # For some reason hashes return false for key? if the value # at that key is nil. It also executes this block for fetch # when it really shouldn't. Detect that error here and give # back nil for data if @attributes.keys.include?(attr_name) nil else # In this case the attribute was truly not found, if we're # given a block execute that otherwise return nil return block_given? ? yield(attr_name) : nil end end # This sets and returns the typecasted value @attributes_cache[attr_name] = self.typecast_attribute_for_read( attr_name, data ) end end |
#read_attribute_before_type_cast(attr_name) ⇒ Object
Reads the attribute directly out of the attributes hash without applying any typecasting if that attribute is not found
327 328 329 |
# File 'lib/api_resource/attributes.rb', line 327 def read_attribute_before_type_cast(attr_name) return @attributes[attr_name.to_sym] end |
#reset_changes ⇒ Object
574 575 576 577 578 579 580 581 |
# File 'lib/api_resource/attributes.rb', line 574 def reset_changes self.class.attribute_names.each do |attr_name| attr_name = attr_name.to_sym reset_attribute!(attr_name) end true end |
#respond_to?(sym, include_private_methods = false) ⇒ Boolean
Override to respond_to? for finding attribute methods even if they are not defined
we should consider private methods
532 533 534 535 536 537 538 539 540 541 |
# File 'lib/api_resource/attributes.rb', line 532 def respond_to?(sym, include_private_methods = false) if sym =~ /\?$/ return true if self.attribute?($`) elsif sym =~ /=$/ return true if self.class.public_attribute_names.include?($`) else return true if self.attribute?(sym.to_sym) end super end |
#save_with_dirty_tracking(*args) ⇒ Boolean
Override for the save method to update our dirty tracking of attributes
embedded in this save provided it succeeds
509 510 511 512 513 514 515 516 517 518 519 520 521 |
# File 'lib/api_resource/attributes.rb', line 509 def save_with_dirty_tracking(*args) if save_without_dirty_tracking(*args) self.make_changes_current if args.first.is_a?(Hash) && args.first[:include_associations] args.first[:include_associations].each do |assoc| Array.wrap(self.send(assoc).internal_object).each(&:make_changes_current) end end return true else return false end end |
#typecast_attribute_for_read(attr_name, value) ⇒ Object
Returns the typecasted value of an attribute for being read (calls from_api on the typecaster). Raises TypecasterNotFound if no typecaster exists for this attribute
385 386 387 388 389 |
# File 'lib/api_resource/attributes.rb', line 385 def typecast_attribute_for_read(attr_name, value) self .find_typecaster(attr_name) .from_api(value) end |
#typecast_attribute_for_write(attr_name, value) ⇒ Object
Returns the typecasted value of the attribute for being written (calls to_api on the typecaster). Raises TypecasterNotFound if no typecaster exists for this attribute
400 401 402 403 404 |
# File 'lib/api_resource/attributes.rb', line 400 def typecast_attribute_for_write(attr_name, value) self .find_typecaster(attr_name) .to_api(value) end |
#write_attribute(attr_name, value) ⇒ Object
Writes an attribute, first typecasting it to the proper type with typecast_attribute_for_write and then setting it in the attributes hash. Raises MissingAttributeError if no such attribute exists
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/api_resource/attributes.rb', line 340 def write_attribute(attr_name, value) attr_name = attr_name.to_sym # Change a write attribute for id to the primary key attr_name = self.class.primary_key if attr_name == :id && self.class.primary_key # The value we expect here should be typecasted for going to # the api typed_value = self.typecast_attribute_for_write(attr_name, value) if attribute_changed?(attr_name) old = changed_attributes[attr_name] changed_attributes.delete(attr_name) if old == typed_value else old = clone_attribute_value(:read_attribute, attr_name) changed_attributes[attr_name] = old if old != typed_value end # Remove this attribute from the attributes cache @attributes_cache.delete(attr_name) # Raise an error if this is not an attribute if !self.attribute?(attr_name) raise ActiveModel::MissingAttributeError.new( "can't write unknown attribute #{attr_name}", caller(0) ) end # Raise another error if this is a protected attribute # if self.protected_attribute?(attr_name) # raise ApiResource::AttributeAccessError.new( # "cannot write to protected attribute #{attr_name}", # caller(0) # ) # end @attributes[attr_name] = typed_value value end |