lib/active_remote/attributes.rb in active_remote-2.4.0 vs lib/active_remote/attributes.rb in active_remote-3.0.0.pre1

- old
+ new

@@ -1,37 +1,194 @@ module ActiveRemote module Attributes + extend ::ActiveSupport::Concern + include ::ActiveModel::AttributeMethods + + included do + attribute_method_suffix "=" + end + + # Performs equality checking on the result of attributes and its type. + # + # @example Compare for equality. + # model == other + # + def ==(other) + return false unless other.instance_of?(self.class) + attributes == other.attributes + end + + # Returns a copy of our attributes hash def attributes - @attributes ||= begin - attribute_names = self.class.attribute_names - Hash[attribute_names.map { |key| [key, send(key)] }] - end @attributes.dup end + # Returns the class name plus its attributes + # + # @example Inspect the model. + # person.inspect + # + def inspect + attribute_descriptions = attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }.join(", ") + separator = " " unless attribute_descriptions.empty? + "#<#{self.class.name}#{separator}#{attribute_descriptions}>" + end + # Read attribute from the attributes hash # def read_attribute(name) name = name.to_s - if respond_to? name + if respond_to?(name) attribute(name) else - raise ActiveAttr::UnknownAttributeError, "unknown attribute: #{name}" + raise ::ActiveRemote::UnknownAttributeError, "unknown attribute: #{name}" end end alias_method :[], :read_attribute # Update an attribute in the attributes hash # def write_attribute(name, value) name = name.to_s - if respond_to? "#{name}=" + if respond_to?("#{name}=") __send__("attribute=", name, value) else - raise ActiveAttr::UnknownAttributeError, "unknown attribute: #{name}" + raise ::ActiveRemote::UnknownAttributeError, "unknown attribute: #{name}" end end alias_method :[]=, :write_attribute + + # Read an attribute from the attributes hash + # + def attribute(name) + @attributes[name] + end + + # Write an attribute to the attributes hash + # + def attribute=(name, value) + @attributes[name] = value + end + + def attribute_method?(attr_name) + # Check if @attributes is defined because dangerous_attribute? method + # can check allocate.respond_to? before actaully calling initialize + defined?(@attributes) && @attributes.key?(attr_name) + end + + module ClassMethods + # Defines an attribute + # + # For each attribute that is defined, a getter and setter will be + # added as an instance method to the model. An + # {AttributeDefinition} instance will be added to result of the + # attributes class method. + # + # @example Define an attribute. + # attribute :name + # + def attribute(name, options={}) + if dangerous_attribute_method_name = dangerous_attribute?(name) + raise ::ActiveRemote::DangerousAttributeError, %{an attribute method named "#{dangerous_attribute_method_name}" would conflict with an existing method} + else + attribute!(name, options) + end + end + + # Defines an attribute without checking for conflicts + # + # Allows you to define an attribute whose methods will conflict + # with an existing method. For example, Ruby's Timeout library + # adds a timeout method to Object. Attempting to define a timeout + # attribute using .attribute will raise a + # {DangerousAttributeError}, but .attribute! will not. + # + # @example Define a dangerous attribute. + # attribute! :timeout + # + def attribute!(name, options={}) + ::ActiveRemote::AttributeDefinition.new(name, options).tap do |attribute_definition| + attribute_name = attribute_definition.name.to_s + # Force active model to generate attribute methods + remove_instance_variable("@attribute_methods_generated") if instance_variable_defined?("@attribute_methods_generated") + define_attribute_methods([attribute_definition.name]) unless attribute_names.include?(attribute_name) + attributes[attribute_name] = attribute_definition + end + end + + # Returns an Array of attribute names as Strings + # + # @example Get attribute names + # Person.attribute_names + # + def attribute_names + attributes.keys + end + + # Returns a Hash of AttributeDefinition instances + # + # @example Get attribute definitions + # Person.attributes + # + def attributes + @attributes ||= ::ActiveSupport::HashWithIndifferentAccess.new + end + + # Determine if a given attribute name is dangerous + # + # Some attribute names can cause conflicts with existing methods + # on an object. For example, an attribute named "timeout" would + # conflict with the timeout method that Ruby's Timeout library + # mixes into Object. + # + # @example Testing a harmless attribute + # Person.dangerous_attribute? :name #=> false + # + # @example Testing a dangerous attribute + # Person.dangerous_attribute? :timeout #=> "timeout" + # + def dangerous_attribute?(name) + return false if attribute_names.include?(name.to_s) + + attribute_methods(name).detect do |method_name| + allocate.respond_to?(method_name, true) + end + end + + # Returns the class name plus its attribute names + # + # @example Inspect the model's definition. + # Person.inspect + # + def inspect + inspected_attributes = attribute_names.sort + attributes_list = "(#{inspected_attributes.join(", ")})" unless inspected_attributes.empty? + "#{name}#{attributes_list}" + end + + protected + + # Assign a set of attribute definitions, used when subclassing models + # + def attributes=(attributes) + @attributes = attributes + end + + private + + # Expand an attribute name into its generated methods names + # + def attribute_methods(name) + attribute_method_matchers.map { |matcher| matcher.method_name(name) } + end + + # Ruby inherited hook to assign superclass attributes to subclasses + # + def inherited(subclass) + super + subclass.attributes = attributes.dup + end + end end end