Class: DataMapper::EmbeddedValue

Inherits:
Object
  • Object
show all
Defined in:
lib/data_mapper/embedded_value.rb

Overview

EmbeddedValue

As an alternative to an extraneous has_one association, EmbeddedValue offers a means to serialize component objects to a table without having to define an entirely new model.

Example:

class Person < DataMapper::Base 

  property :name, :string
  property :occupation, :string

  embed :address, :prefix => true do
    property :street, :string
    property :city, :string
    property :state, :string, :size => 2
    property :zip_code, :string, :size => 10

    def city_state_zip_code
      "#{city}, #{state} #{zip_code}"
    end

  end 
end

Columns for the Address model will appear in the Person table. Passing :prefix => true will prefix the column name with the parent table’s name. The default behavior is to use the columns as they are defined. Using the above example, the database table structure will become:

Column                      Datatype, Options
===============================================================
name                         :string
occupation                   :string
address_street               :string
address_city                 :string
address_state                :string, :size => 2
address_zip_code             :string, :size => 10

EmbeddedValue’s become instance methods off of an instance of the parent class and return a sub-class of the parent class.

bob = Person.first(:name => 'Bob')
bob.address                         # => #<Person::Address:0x1a492b8>
bob.address.city                    # => "Pittsburgh"
bob.address.city_state_zip_code     # => "Pitsburgh, PA 90210"

Constant Summary collapse

EMBEDDED_PROPERTIES =
[]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(instance) ⇒ EmbeddedValue

Returns a new instance of EmbeddedValue.



52
53
54
55
# File 'lib/data_mapper/embedded_value.rb', line 52

def initialize(instance)
  @instance = instance
  @container_prefix = ''
end

Class Method Details

.containing_classObject

returns the class in which the EmbeddedValue is declared



107
108
109
110
111
112
113
# File 'lib/data_mapper/embedded_value.rb', line 107

def self.containing_class
  @containing_class || @containing_class = begin
    tree = name.split('::')
    tree.pop
    tree.inject(Object) { |klass, current| klass.const_get(current) }
  end
end

.define(container, name, options, &block) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/data_mapper/embedded_value.rb', line 115

def self.define(container, name, options, &block)
  embedded_class, embedded_class_name, accessor_name = nil

  accessor_name = name.to_s
  embedded_class_name = Inflector.camelize(accessor_name)
  embedded_class = Class.new(EmbeddedValue)
  container.const_set(embedded_class_name, embedded_class) unless container.const_defined?(embedded_class_name)

  if options[:prefix]
    container_prefix = options[:prefix].kind_of?(String) ? options[:prefix] : "#{accessor_name}_"
    embedded_class.instance_variable_set('@container_prefix', container_prefix)
  end

  embedded_class.instance_variable_set('@containing_class', container)

  embedded_class.instance_variable_set('@container_lazy', !!options[:lazy])
  embedded_class.instance_variable_set('@container_reader_visibility', options[:reader] || options[:accessor] || :public)
  embedded_class.instance_variable_set('@container_writer_visibility', options[:writer] || options[:accessor] || :public)

  embedded_class.class_eval(&block) if block_given?

  container.class_eval <<-EOS
    def #{accessor_name}
      #{embedded_class_name}.new(self)
    end
  EOS
end

.define_property_getter(name, property) ⇒ Object

define embedded property getters



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/data_mapper/embedded_value.rb', line 77

def self.define_property_getter(name, property) # :nodoc:

  # add the method on the embedded class
  class_eval <<-EOS
    #{property.reader_visibility.to_s}
    def #{name}
      #{"@instance.lazy_load!("+ property.name.inspect + ")" if property.lazy?}
      @instance.instance_variable_get(#{property.instance_variable_name.inspect})
    end
  EOS

  # add a shortcut boolean? method if applicable (ex: activated?)
  if property.type == :boolean
    class_eval("alias #{property.name}? #{property.name}")
  end
end

.define_property_setter(name, property) ⇒ Object

define embedded property setters



95
96
97
98
99
100
101
102
103
104
# File 'lib/data_mapper/embedded_value.rb', line 95

def self.define_property_setter(name, property)  # :nodoc:

  # add the method on the embedded class
  class_eval <<-EOS
    #{property.writer_visibility.to_s}
    def #{name.to_s.sub(/\?$/, '')}=(value)
      @instance.instance_variable_set(#{property.instance_variable_name.inspect}, value)
    end
  EOS
end

.inherited(base) ⇒ Object



57
58
59
# File 'lib/data_mapper/embedded_value.rb', line 57

def self.inherited(base)
  base.const_set('EMBEDDED_PROPERTIES', [])
end

.property(name, type, options = {}) ⇒ Object

add an embedded property. For more information about how to define properties, visit Property.



62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/data_mapper/embedded_value.rb', line 62

def self.property(name, type, options = {})
  # set lazy option on the mapping if defined in the embed block
  options[:lazy] ||= @container_lazy
  
  options[:reader] ||= options[:accessor] || @container_reader_visibility
  options[:writer] ||= options[:accessor] || @container_writer_visibility
  
  property_name = @container_prefix ? @container_prefix + name.to_s : name
  
  property = containing_class.property(property_name, type, options)
  define_property_getter(name, property)
  define_property_setter(name, property)
end