# encoding: utf-8
module Mongoid #:nodoc:
module Attributes
def self.included(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module InstanceMethods
# Get the id associated with this object. This will pull the _id value out
# of the attributes +Hash+.
def id
@attributes["_id"]
end
# Set the id of the +Document+ to a new one.
def id=(new_id)
@attributes["_id"] = new_id
end
alias :_id :id
alias :_id= :id=
# Used for allowing accessor methods for dynamic attributes.
def method_missing(name, *args)
attr = name.to_s
return super unless @attributes.has_key?(attr.reader)
if attr.writer?
# "args.size > 1" allows to simulate 1.8 behavior of "*args"
@attributes[attr.reader] = (args.size > 1) ? args : args.first
else
@attributes[attr.reader]
end
end
# Process the provided attributes casting them to their proper values if a
# field exists for them on the +Document+. This will be limited to only the
# attributes provided in the suppied +Hash+ so that no extra nil values get
# put into the document's attributes.
def process(attrs = {})
attrs.each_pair do |key, value|
if Mongoid.allow_dynamic_fields && !respond_to?("#{key}=")
@attributes[key.to_s] = value
else
send("#{key}=", value) if value
end
end
end
# Read a value from the +Document+ attributes. If the value does not exist
# it will return nil.
#
# Options:
#
# name: The name of the attribute to get.
#
# Example:
#
# person.read_attribute(:title)
def read_attribute(name)
access = name.to_s
fields[access].get(@attributes[access])
end
# Remove a value from the +Document+ attributes. If the value does not exist
# it will fail gracefully.
#
# Options:
#
# name: The name of the attribute to remove.
#
# Example:
#
# person.remove_attribute(:title)
def remove_attribute(name)
@attributes.delete(name.to_s)
end
# Returns the object type. This corresponds to the name of the class that
# this +Document+ is, which is used in determining the class to
# instantiate in various cases.
def _type
@attributes["_type"]
end
# Set the type of the +Document+. This should be the name of the class.
def _type=(new_type)
@attributes["_type"] = new_type
end
# Write a single attribute to the +Document+ attribute +Hash+. This will
# also fire the before and after update callbacks, and perform any
# necessary typecasting.
#
# Options:
#
# name: The name of the attribute to update.
# value: The value to set for the attribute.
#
# Example:
#
# person.write_attribute(:title, "Mr.")
#
# This will also cause the observing +Document+ to notify it's parent if
# there is any.
def write_attribute(name, value)
access = name.to_s
@attributes[access] = fields[access].set(value)
notify unless id.blank?
end
# Writes the supplied attributes +Hash+ to the +Document+. This will only
# overwrite existing attributes if they are present in the new +Hash+, all
# others will be preserved.
#
# Options:
#
# attrs: The +Hash+ of new attributes to set on the +Document+
#
# Example:
#
# person.write_attributes(:title => "Mr.")
#
# This will also cause the observing +Document+ to notify it's parent if
# there is any.
def write_attributes(attrs = nil)
process(attrs || {})
identify if id.blank?
notify
end
protected
# Used when supplying a :reject_if block as an option to
# accepts_nested_attributes_for
def reject(attributes, options)
rejector = options[:reject_if]
if rejector
attributes.delete_if do |key, value|
rejector.call(value)
end
end
end
end
module ClassMethods
# Defines attribute setters for the associations specified by the names.
# This will work for a has one or has many association.
#
# Example:
#
# class Person
# include Mongoid::Document
# has_one :name
# has_many :addresses
#
# accepts_nested_attributes_for :name, :addresses
# end
def accepts_nested_attributes_for(*args)
associations = args.flatten
options = associations.last.is_a?(Hash) ? associations.pop : {}
associations.each do |name|
define_method("#{name}_attributes=") do |attrs|
reject(attrs, options)
association = send(name)
if association
update(association, true)
association.nested_build(attrs)
else
send("build_#{name}", attrs)
end
end
end
end
end
end
end