Module: DataMapper::NestedAttributes::ClassMethods

Defined in:
lib/dm-accepts_nested_attributes/nested_attributes.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



6
7
8
9
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 6

def self.extended(base)
  base.class_inheritable_accessor :autosave_associations
  base.autosave_associations = {}
end

Instance Method Details

#accepts_nested_attributes_for(association_name, options = {}) ⇒ Object

Defines an attributes reader and writer for the specified association(s). If you are using attr_protected or attr_accessible, then you will need to add the attribute writer to the allowed list.

After any params are passed to the attributes writer they are available via the attributes reader (they are stored in an instance variable of the same name). The attributes reader returns nil if the attributes writer has not been called.

Supported options:

:allow_destroy

If true, destroys any members from the attributes hash with a _delete key and a value that evaluates to true (eg. 1, ‘1’, true, or ‘true’). This option is off by default.

:reject_if

Allows you to specify a Proc that checks whether a record should be built for a certain attribute hash. The hash is passed to the Proc and the Proc should return either true or false. When no Proc is specified a record will be built for all attribute hashes that do not have a _delete that evaluates to true.

Examples: # creates avatar_attributes # creates avatar_attributes= accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes.blank? } # creates avatar_attributes and posts_attributes # creates avatar_attributes= and posts_attributes= accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 39

def accepts_nested_attributes_for(association_name, options = {})
  
  assert_kind_of 'association_name', association_name, Symbol, String
  assert_kind_of 'options',          options,          Hash
  
  options = { :allow_destroy => false }.update(options)
  
  # raises if the specified option keys aren't valid
  assert_valid_autosave_options(options)
  
  # raises if the specified association doesn't exist
  # we don't need the return value here, just the check
  # ------------------------------------------------------
  # also, when using the return value from this call to
  # replace association_name with association.name,
  # has(1, :through) are broken, because they seem to have
  # a different name
  
  association_for_name(association_name)
  
  
  # should be safe to go on
  
  include InstanceMethods
  
  if ::DataMapper.const_defined?('Validate')

    require Pathname(__FILE__).dirname.expand_path + 'association_validation'

    include AssociationValidation

  end
  
  autosave_associations[association_name] = options
  
  type = nr_of_possible_child_instances(association_name) > 1 ? :collection : :one_to_one
  
  class_eval %{
    
    def save(context = :default)
      saved = false # preserve Resource#save api contract
      transaction { |t| t.rollback unless saved = super }
      saved
    end
    
    def #{association_name}_attributes
      @#{association_name}_attributes
    end
    
    def #{association_name}_attributes=(attributes)
      attributes = sanitize_nested_attributes(attributes)
      @#{association_name}_attributes = attributes
      assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]})
    end
    
    if association_type(:#{association_name}) == :many_to_one || association_type(:#{association_name}) == :one_to_one
    
      def get_#{association_name}
        #{association_name.to_s} || self.class.associated_model_for_name(:#{association_name}).new
      end
    
    end
    
  }, __FILE__, __LINE__ + 1
  
end

#associated_model_for_name(association_name, repository = :default) ⇒ Object

i have the feeling this should be refactored



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 119

def associated_model_for_name(association_name, repository = :default)
  a = association_for_name(association_name, repository)
  case association_type(association_name)
  when :many_to_one
    a.parent_model
  when :one_to_one
    a.child_model
  when :one_to_many
    a.child_model
  when :many_to_many
    Object.full_const_get(a.options[:child_model])
  else
    raise ArgumentError, "Unknown association type #{a.inspect}"
  end
end

#association_for_name(name, repository = :default) ⇒ Object

avoid nil access by always going through this this method raises if the association named name is not established in this model

Raises:

  • (ArgumentError)


154
155
156
157
158
159
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 154

def association_for_name(name, repository = :default)
  association = self.relationships(repository)[name]
  # TODO think about using a specific Error class like UnknownAssociationError
  raise(ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}") unless association
  association
end

#association_type(association_name) ⇒ Object

maybe this should be provided by dm-core somehow DataMapper::Association::Relationship would be a place maybe?



137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 137

def association_type(association_name)
  a = association_for_name(association_name)
  if a.options[:max].nil? # belongs_to
    :many_to_one
  elsif a.options[:max] == 1 # has(1)
    :one_to_one
  elsif a.options[:max] > 1 && !a.is_a?(DataMapper::Associations::RelationshipChain) # has(n)
    :one_to_many
  elsif a.is_a?(DataMapper::Associations::RelationshipChain) # has(n, :through) MUST be checked after has(n) here
    :many_to_many
  else
    raise ArgumentError, "Unknown association type #{a.inspect}"
  end
end

#nr_of_possible_child_instances(association_name, repository = :default) ⇒ Object

utility methods



113
114
115
116
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 113

def nr_of_possible_child_instances(association_name, repository = :default)
  # belongs_to seems to generate no options[:max]
  association_for_name(association_name, repository).options[:max] || 1
end

#reject_new_nested_attributes_proc_for(association_name) ⇒ Object



106
107
108
# File 'lib/dm-accepts_nested_attributes/nested_attributes.rb', line 106

def reject_new_nested_attributes_proc_for(association_name)
  autosave_associations[association_name] ? autosave_associations[association_name][:reject_if] : nil
end