cody-dot-js / codyaprice.com-legacy

Personal website
https://codyaprice.com
MIT License
1 stars 0 forks source link

[blog post] Rails assign_attributes fails hard instead of filtering out unused attrs #122

Open cody-dot-js opened 4 years ago

cody-dot-js commented 4 years ago

rails/attribute_assignment.rb at 09a2979f75c51afb797dd60261a8930f84144af8 Β· rails/rails Β· GitHub

def _assign_attribute(k, v)
  setter = :"#{k}="
  if respond_to?(setter)
    public_send(setter, v)
  else
    raise UnknownAttributeError.new(self, k) # πŸ‘ˆπŸ‘ˆπŸ‘ˆ
  end
end
# The following methods were pulled from Rails ActiveModel and modified
# to not raise an UnknownAttributeError when an attribute in the data we
# get back from an API, like the Identity Realm API, does not have a
# corresponding setter method (attr_accessor/attr_writer) defined in a Class.
# https://github.com/rails/rails/blob/09a2979f75c51afb797dd60261a8930f84144af8/activemodel/lib/active_model/attribute_assignment.rb
#
# Example: A "Car" class that uses this model defines attr_accessors for :make, :model, and :year.
# The Car API that we get car data from only returns make, model, and year right now, so all is well.
# Eventually the Car API adds "color" to the data that it returns. With the standard Rails ActiveModel
# this would cause an UnknownAttributeError because our Car class does not define a :color setter.
# Instead, with the modified code below all of the attributes we have defined will be assigned a value
# and any extra data attributes that come from the API will simply be dropped w/o disrupting the application.
#
# This makes our classes that use this model more resilient to API changes (that we don't control),
# AND allows us to ignore fields like "last_updated" and "first_created" that an API may include w/ data.
def assign_attributes(new_attributes)
  if !new_attributes.respond_to?(:stringify_keys)
    raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
  end
  return if new_attributes.empty?

  attributes = new_attributes.stringify_keys
  _assign_attributes(attributes)
end

def _assign_attributes(attributes)
  attributes.each do |k, v|
    _assign_attribute(k, v)
  end
end

def _assign_attribute(k, v)
  setter = :"#{k}="
  if respond_to?(setter)
    public_send(setter, v)
  end
end

shoutout to my man @SamL_not_SAML