Skip to content

Instantly share code, notes, and snippets.

@danott
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danott/d82b3c8fea686371115d to your computer and use it in GitHub Desktop.
Save danott/d82b3c8fea686371115d to your computer and use it in GitHub Desktop.
Rails' attr_readonly prevents updates from being written to the database, but does not prevent them from being changed in memory. If you have derived values from what's in memory, this can be problematic. For rolling out a new feature, I want to know if any of our code paths are trying to update the field. I don't want it to fail silently and be…
# Creating a whole new concern, introducing the `attr_immutable` macro
module ImmutableAttributes
class ImmutableAttributeError < StandardError; end
extend ActiveSupport::Concern
included do
class_attribute :_attr_immutable, instance_accessor: false
self._attr_immutable = []
end
module ClassMethods
def attr_immutable(*attributes)
self._attr_immutable = Set.new(attributes.map(&:to_s)) + (immutable_attributes)
attributes.each do |attribute|
define_method("#{attribute}=") do |value|
if !new_record?
raise ImmutableAttributeError.new("#{self.class}##{attribute} is immutable")
else
super(value)
end
end
end
end
def immutable_attributes
self._attr_immutable
end
end
def []=(attribute, value)
if !new_record? && self.class.immutable_attributes.include?(attribute.to_s)
raise ImmutableAttributeError.new("#{self.class}##{attribute} is immutable")
else
super(attribute, value)
end
end
end
class Event
include ImmutableAttributes
attr_immutable :starts_at
end
# Piggy backing on Rails' `attr_readonly`, and making it fail loudly when trying to
# change the value in memory.
module NoisyReadonlyAttributes
extend ActiveSupport::Concern
class NoisyReadonlyAttributeError < StandardError; end
included do
after_initialize make_readonly_attrs_noisy
end
def []=(attribute, value)
if !new_record? && self.class.readonly_attributes.include?(attribute.to_s)
raise NoisyReadonlyAttributeError.new("#{self.class}##{attribute} is read only")
else
super(attribute, value)
end
end
private
def make_readonly_attrs_noisy
self.class.readonly_attributes.each do |attribute|
define_singleton_method("#{attribute}=") do |value|
if !new_record?
raise NoisyReadonlyAttributeError.new("#{self.class}##{attribute} is read only")
else
super(value)
end
end
end
end
end
# Example Usage
class Event
include NoisyReadonlyAttributes
attr_readonly :starts_at
end
@rmosolgo
Copy link

rmosolgo commented Nov 4, 2014

👍 seems like a good feature if the Rails built-in isn't cutting it!

Am I supposed to pick a favorite? I prefer attr_immutable

@danott
Copy link
Author

danott commented Nov 4, 2014

@rmosolgo yep, looking for the better of the two. Or if there's a third that is better than both of them, I wanna know about that!

@mranallo
Copy link

mranallo commented Nov 4, 2014

👍 Great use of a concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment