custom_fields 1.1.0.rc1 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README.textile +14 -6
  3. data/config/locales/fr.yml +5 -1
  4. data/lib/custom_fields.rb +11 -37
  5. data/lib/custom_fields/extensions/carrierwave.rb +25 -0
  6. data/lib/custom_fields/extensions/mongoid/document.rb +12 -50
  7. data/lib/custom_fields/extensions/mongoid/factory.rb +20 -0
  8. data/lib/custom_fields/extensions/mongoid/fields.rb +29 -0
  9. data/lib/custom_fields/extensions/mongoid/fields/i18n.rb +53 -0
  10. data/lib/custom_fields/extensions/mongoid/fields/internal/localized.rb +84 -0
  11. data/lib/custom_fields/extensions/mongoid/relations/referenced/many.rb +29 -0
  12. data/lib/custom_fields/field.rb +44 -175
  13. data/lib/custom_fields/source.rb +333 -0
  14. data/lib/custom_fields/target.rb +90 -0
  15. data/lib/custom_fields/types/boolean.rb +26 -3
  16. data/lib/custom_fields/types/date.rb +36 -24
  17. data/lib/custom_fields/types/default.rb +44 -24
  18. data/lib/custom_fields/types/file.rb +35 -17
  19. data/lib/custom_fields/types/select.rb +184 -0
  20. data/lib/custom_fields/types/string.rb +25 -6
  21. data/lib/custom_fields/types/text.rb +35 -8
  22. data/lib/custom_fields/types_old/boolean.rb +13 -0
  23. data/lib/custom_fields/{types → types_old}/category.rb +0 -0
  24. data/lib/custom_fields/types_old/date.rb +49 -0
  25. data/lib/custom_fields/types_old/default.rb +44 -0
  26. data/lib/custom_fields/types_old/file.rb +27 -0
  27. data/lib/custom_fields/{types → types_old}/has_many.rb +0 -0
  28. data/lib/custom_fields/{types → types_old}/has_many/proxy_collection.rb +0 -0
  29. data/lib/custom_fields/{types → types_old}/has_many/reverse_lookup_proxy_collection.rb +0 -0
  30. data/lib/custom_fields/{types → types_old}/has_one.rb +0 -0
  31. data/lib/custom_fields/types_old/string.rb +13 -0
  32. data/lib/custom_fields/types_old/text.rb +15 -0
  33. data/lib/custom_fields/version.rb +1 -1
  34. metadata +115 -91
  35. data/lib/custom_fields/custom_fields_for.rb +0 -350
  36. data/lib/custom_fields/extensions/mongoid/relations/accessors.rb +0 -31
  37. data/lib/custom_fields/extensions/mongoid/relations/builders.rb +0 -30
  38. data/lib/custom_fields/proxy_class/base.rb +0 -112
  39. data/lib/custom_fields/proxy_class/builder.rb +0 -60
  40. data/lib/custom_fields/proxy_class/helper.rb +0 -57
  41. data/lib/custom_fields/self_metadata.rb +0 -30
@@ -1,228 +1,97 @@
1
1
  module CustomFields
2
2
 
3
3
  class Field
4
+
4
5
  include ::Mongoid::Document
5
6
  include ::Mongoid::Timestamps
6
7
 
7
- # types ##
8
- include Types::Default
9
- include Types::String
10
- include Types::Text
11
- include Types::Category
12
- include Types::Boolean
13
- include Types::Date
14
- include Types::File
15
- include Types::HasOne
16
- include Types::HasMany
8
+ ## types ##
9
+ %w(default string text date boolean file select).each do |type|
10
+ include "CustomFields::Types::#{type.classify}::Field".constantize
11
+ end
17
12
 
18
13
  ## fields ##
19
14
  field :label
20
- field :_alias
21
- field :_name
22
- field :kind
15
+ field :name
16
+ field :type
23
17
  field :hint
24
- field :position, :type => Integer, :default => 0
25
- field :required, :type => Boolean, :default => false
18
+ field :position, :type => Integer, :default => 0
19
+ field :required, :type => Boolean, :default => false
20
+ field :localized, :type => Boolean, :default => false
26
21
 
27
22
  ## validations ##
28
- validates_presence_of :label, :kind
29
- validates_exclusion_of :_alias, :in => lambda { |f| CustomFields.options[:reserved_aliases].map(&:to_s) }
30
- validates_format_of :_alias, :with => /^[a-z]([A-Za-z0-9_]+)?$/
31
- validate :uniqueness_of_label_and_alias
32
-
33
- ## other accessors ##
34
- attr_accessor :parentized_done # for performance purpose
23
+ validates_presence_of :label, :type
24
+ validates_exclusion_of :name, :in => lambda { |f| CustomFields.options[:reserved_names].map(&:to_s) }
25
+ validates_format_of :name, :with => /^[a-z]([A-Za-z0-9_]+)?$/
26
+ validate :uniqueness_of_label_and_name
35
27
 
36
28
  ## callbacks ##
37
- before_validation :set_alias
38
- before_save :invalidate_proxy_klass
29
+ before_validation :set_name
39
30
 
40
31
  ## methods ##
41
32
 
42
- # Returns the type class related to this field
33
+ # Builds the mongodb updates based on
34
+ # the new state of the field.
35
+ # Call a different method if the field has a different behaviour.
43
36
  #
44
- # @return [ Class ] The class defining the field type
37
+ # @param [ Hash ] memo Store the updates
45
38
  #
46
- def field_type
47
- self.class.field_types[self.safe_kind.to_sym]
48
- end
49
-
50
- # Enhance a document class by applying to it the information stored
51
- # in the type related to this field
52
- #
53
- # @param [ Class ] klass The document class
39
+ # @return [ Hash ] The memo object upgraded
54
40
  #
55
- def apply(klass)
56
- klass.field self._name, :type => self.field_type if self.field_type
57
-
58
- apply_method_name = :"apply_#{self.safe_kind}_type"
41
+ def collect_diff(memo)
42
+ method_name = :"collect_#{self.type}_diff"
59
43
 
60
- if self.respond_to?(apply_method_name)
61
- self.send(apply_method_name, klass)
44
+ if self.respond_to?(method_name)
45
+ self.send(method_name, memo)
62
46
  else
63
- apply_default_type(klass)
64
- end
65
-
66
- validation_method_name = :"add_#{self.safe_kind}_validation"
67
-
68
- if self.respond_to?(validation_method_name)
69
- self.send(validation_method_name, klass)
70
- else
71
- add_default_validation(klass)
47
+ collect_default_diff(memo)
72
48
  end
73
49
  end
74
50
 
75
- # Make sure it returns a valid alias
51
+ # Returns the information (name, type, required, ...etc) needed to build
52
+ # the custom class.
53
+ # That will be stored into the target instance.
76
54
  #
77
- # @return [ String ] the alias
55
+ # @return [ Hash ] The hash
78
56
  #
79
- def safe_alias
80
- self.set_alias
81
- self._alias
82
- end
83
-
84
- # Returns the kind (or type) of the current field.
85
- # Because of compatibility purpose, prior version of CustomFields used to have the value of kind in uppercase.
86
- #
87
- # @return [ String ] the kind
88
- #
89
- def safe_kind
90
- self.kind.downcase
91
- end
92
-
93
- # Returns the name of the relation binding this field and the custom_fields in the parent class
94
- #
95
- # @example:
96
- # class Company
97
- # embeds_many :employees
98
- # custom_fields_for :employees
99
- # end
100
- #
101
- # field = company.employees_custom_fields.build :label => 'His/her position', :_alias => 'position', :kind => 'string'
102
- # field.custom_fields_relation_name == 'employees'
103
- #
104
- # @return [ String ] the relation's name
105
- #
106
- def custom_fields_relation_name
107
- self.metadata.name.to_s.gsub('_custom_fields', '')
108
- end
109
-
110
- # Checks if the field is valid without running the callback which marks
111
- # the proxy class as invalidated
112
- #
113
- # @return [ Boolean ] true if the field has no errors, false otherwise
114
- def quick_valid?
115
- # true
116
- # CustomFields::Field.without_callback(:validation, :after, :invalidate_proxy_klass) do
117
- CustomFields::Field.without_callback(:save, :before, :invalidate_proxy_klass) do
118
- self.valid?
119
- end
120
- end
57
+ def to_recipe
58
+ method_name = :"#{self.type}_to_recipe"
59
+ custom_to_recipe = self.send(method_name) rescue {}
121
60
 
122
- # Destroys a field and saves the parent too in order to keep track of the changes
123
- # for the parent (for instance, the version has to be bumped).
124
- #
125
- # @param [ Hash ] options Options to pass to destroy.
126
- #
127
- # @return [ true, false ] True if successful, false if not.
128
- #
129
- def destroy(options = {})
130
- super.tap do
131
- self.mark_proxy_klass_flag_as_invalidated
132
- self._parent.save
133
- end
61
+ { 'name' => self.name, 'type' => self.type, 'required' => self.required?, 'localized' => self.localized? }.merge(custom_to_recipe)
134
62
  end
135
63
 
136
- # Collects all the important attributes of this field.
137
- # It also accepts an extra hash which will be merged with
138
- # the one built by this method (by default, this is an empty hash)
139
- #
140
- # @param [ Hash ] more The extra hash
141
- #
142
- # @return [ Hash ] the hash
143
- #
144
- def to_hash(more = {})
145
- self.fields.keys.inject({}) do |memo, meth|
146
- memo[meth] = self.send(meth.to_sym); memo
147
- end.tap do |hash|
148
- self.class.field_types.keys.each do |type|
149
- if self.respond_to?(:"#{type}_to_hash")
150
- hash.merge!(self.send(:"#{type}_to_hash"))
151
- end
152
- end
153
- end.merge({
154
- 'id' => self._id,
155
- 'new_record' => self.new_record?,
156
- 'errors' => self.errors,
157
- 'kind_name' => I18n.t("custom_fields.kind.#{self.safe_kind}")
158
- }).merge(more)
159
- end
64
+ def as_json(options = {})
65
+ method_name = :"#{self.type}_as_json"
66
+ custom_as_json = self.send(method_name) rescue {}
160
67
 
161
- # Overides the default behaviour of the to_json method by using the to_hash method
162
- #
163
- # @return [ String ] the json object
164
- #
165
- def to_json
166
- ActiveSupport::JSON.encode(self.to_hash)
68
+ super(options).merge(custom_as_json)
167
69
  end
168
70
 
169
71
  protected
170
72
 
171
- def uniqueness_of_label_and_alias
73
+ def uniqueness_of_label_and_name
172
74
  if self.siblings.any? { |f| f.label == self.label && f._id != self._id }
173
75
  self.errors.add(:label, :taken)
174
76
  end
175
77
 
176
- if self.siblings.any? { |f| f._alias == self._alias && f._id != self._id }
177
- self.errors.add(:_alias, :taken)
78
+ if self.siblings.any? { |f| f.name == self.name && f._id != self._id }
79
+ self.errors.add(:name, :taken)
178
80
  end
179
81
  end
180
82
 
181
- def set_unique_name!
182
- self._name ||= "custom_field_#{self.increment_counter!}"
183
- end
184
-
185
- def set_alias
186
- return if self.label.blank? && self._alias.blank?
83
+ def set_name
84
+ return if self.label.blank? && self.name.blank?
187
85
 
188
- if self._alias.blank?
189
- self._alias = self.label.parameterize('_').gsub('-', '_').downcase
86
+ if self.name.blank?
87
+ self.name = self.label.parameterize('_').gsub('-', '_').downcase
190
88
  end
191
89
  end
192
90
 
193
- def increment_counter!
194
- name = self.custom_fields_relation_name
195
- self._parent.bump_custom_fields_counter(name)
196
- end
197
-
198
91
  def siblings
199
92
  self._parent.send(self.metadata.name)
200
93
  end
201
94
 
202
- def parentize_with_custom_fields(object)
203
- return if self.parentized_done
204
-
205
- parentize_without_custom_fields(object)
206
-
207
- self.send(:set_unique_name!)
208
-
209
- self.parentized_done = true
210
- end
211
- alias_method_chain :parentize, :custom_fields
212
-
213
- def invalidate_proxy_klass
214
- # puts "#{self._name} / #{self._alias} _ invalidate_proxy_klass !!! #{self.flagged_for_destroy?} / #{self.destroyed?}"
215
- if self.changed? || self.flagged_for_destroy?
216
- self.mark_proxy_klass_flag_as_invalidated
217
- end
218
- end
219
-
220
- def mark_proxy_klass_flag_as_invalidated
221
- # puts "\t*** [mark_proxy_klass_flag_as_invalidated] called for '#{self._name}'"
222
- name = self.custom_fields_relation_name
223
- self._parent.mark_klass_with_custom_fields_as_invalidated(name)
224
- end
225
-
226
95
  end
227
96
 
228
97
  end
@@ -0,0 +1,333 @@
1
+ module CustomFields
2
+
3
+ module Source
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ cattr_accessor :_custom_fields_for
9
+ self._custom_fields_for = []
10
+
11
+ attr_accessor :_custom_fields_diff
12
+ attr_accessor :_custom_field_localize_diff
13
+ end
14
+
15
+ module InstanceMethods
16
+
17
+ # Determines if the relation is enhanced by the custom fields
18
+ #
19
+ # @example the Person class has somewhere in its code this: "custom_fields_for :addresses"
20
+ # person.custom_fields_for?(:addresses)
21
+ #
22
+ # @param [ String, Symbol ] name The name of the relation.
23
+ #
24
+ # @return [ true, false ] True if enhanced, false if not.
25
+ #
26
+ def custom_fields_for?(name)
27
+ self.class.custom_fields_for?(name)
28
+ end
29
+
30
+ # Returns the class enhanced by the custom fields.
31
+ # Be careful, call this method only if the source class
32
+ # has been saved with success.
33
+ #
34
+ # @param [ String, Symbol ] name The name of the relation.
35
+ #
36
+ # @return [ Class ] The modified class.
37
+ #
38
+ def klass_with_custom_fields(name)
39
+ recipe = self.custom_fields_recipe_for(name)
40
+ target = self.send(name).metadata.klass
41
+ target.klass_with_custom_fields(recipe)
42
+ end
43
+
44
+ # Returns the ordered list of custom fields for a relation
45
+ #
46
+ # @example the Person class has somewhere in its code this: "custom_fields_for :addresses"
47
+ # person.ordered_custom_fields(:addresses)
48
+ #
49
+ # @param [ String, Symbol ] name The name of the relation.
50
+ #
51
+ # @return [ Collection ] The ordered list.
52
+ #
53
+ def ordered_custom_fields(name)
54
+ self.send(:"#{name}_custom_fields").sort { |a, b| (a.position || 0) <=> (b.position || 0) }
55
+ end
56
+
57
+ # Returns the recipe (meaning all the rules) needed to
58
+ # build the custom klass
59
+ #
60
+ # @param [ String, Symbol ] name The name of the relation.
61
+ #
62
+ # @return [ Array ] An array of hashes
63
+ #
64
+ def custom_fields_recipe_for(name)
65
+ {
66
+ 'name' => "#{name.to_s.classify}#{self._id}",
67
+ 'rules' => self.ordered_custom_fields(name).map(&:to_recipe),
68
+ 'version' => self.custom_fields_version(name)
69
+ }
70
+ end
71
+
72
+ # Returns the number of the version for relation with custom fields
73
+ #
74
+ # @param [ String, Symbol ] name The name of the relation.
75
+ #
76
+ # @return [ Integer ] The version number
77
+ #
78
+ def custom_fields_version(name)
79
+ self.send(:"#{name}_custom_fields_version") || 0
80
+ end
81
+
82
+ # When the fields have been modified and before the object is saved,
83
+ # we bump the version.
84
+ #
85
+ # @param [ String, Symbol ] name The name of the relation.
86
+ #
87
+ def bump_custom_fields_version(name)
88
+ version = self.custom_fields_version(name) + 1
89
+ self.send(:"#{name}_custom_fields_version=", version)
90
+ end
91
+
92
+ # Change the metadata of a relation enhanced by the custom fields.
93
+ # In Mongoid, all the instances of a same document share the same metadata objects.
94
+ #
95
+ # @param [ String, Symbol ] name The name of the relation.
96
+ #
97
+ def refresh_metadata_with_custom_fields(name)
98
+ return if !self.persisted? || self.send(:"#{name}_custom_fields").blank? # do not generate a klass without all the information
99
+
100
+ old_metadata = self.send(name).metadata
101
+
102
+ # puts "old_metadata = #{old_metadata.klass.inspect} / #{old_metadata.object_id.inspect}" # DEBUG
103
+
104
+ self.send(name).metadata = old_metadata.clone.tap do |metadata|
105
+ metadata.instance_variable_set(:@klass, self.klass_with_custom_fields(name))
106
+ end
107
+
108
+ # puts "new_metadata = #{self.send(name).metadata.klass.inspect} / #{self.send(name).metadata.object_id.inspect}" # DEBUG
109
+ end
110
+
111
+ # Initializes the object tracking the modifications
112
+ # of the custom fields
113
+ #
114
+ # @param [ String, Symbol ] name The name of the relation.
115
+ #
116
+ def initialize_custom_fields_diff(name)
117
+ self._custom_field_localize_diff ||= Hash.new([])
118
+
119
+ self._custom_fields_diff ||= {}
120
+ self._custom_fields_diff[name] = { '$set' => {}, '$unset' => {}, '$rename' => {} }
121
+ end
122
+
123
+ # Collects all the modifications of the custom fields
124
+ #
125
+ # @param [ String, Symbol ] name The name of the relation.
126
+ #
127
+ # @return [ Array ] An array of hashes storing the modifications
128
+ #
129
+ def collect_custom_fields_diff(name, fields)
130
+ # puts "==> collect_custom_fields_diff for #{name}, #{fields.size}" # DEBUG
131
+
132
+ memo = self.initialize_custom_fields_diff(name)
133
+
134
+ fields.map do |field|
135
+ field.collect_diff(memo)
136
+ end
137
+
138
+ # collect fields with a modified localized field
139
+ fields.each do |field|
140
+ if field.localized_changed? && field.persisted?
141
+ self._custom_field_localize_diff[name] << { :field => field.name, :localized => field.localized? }
142
+ end
143
+ end
144
+ end
145
+
146
+ # Apply the modifications collected from the custom fields by
147
+ # updating all the documents of the relation.
148
+ # The update uses the power of mongodb to make it fully optimized.
149
+ #
150
+ # @param [ String, Symbol ] name The name of the relation.
151
+ #
152
+ def apply_custom_fields_diff(name)
153
+ # puts "==> apply_custom_fields_recipes for #{name}, #{self._custom_fields_diff[name].inspect}" # DEBUG
154
+
155
+ operations = self._custom_fields_diff[name]
156
+ operations['$set'].merge!({ 'custom_fields_recipe.version' => self.custom_fields_version(name) })
157
+ collection, selector = self.send(name).collection, self.send(name).criteria.selector
158
+
159
+ # puts "selector = #{selector.inspect}, memo = #{attributes.inspect}" # DEBUG
160
+
161
+ collection.update selector, operations, :multi => true
162
+ end
163
+
164
+ # If the localized attribute has been changed in at least one of the custom fields,
165
+ # we have to upgrade all the records enhanced by custom_fields in order to make
166
+ # the values consistent with the mongoid localize option.
167
+ #
168
+ # Ex: post.attributes[:name] = 'Hello world' => post.attributes[:name] = { :en => 'Hello world' }
169
+ #
170
+ # @param [ String, Symbol ] name The name of the relation.
171
+ #
172
+ def apply_custom_fields_localize_diff(name)
173
+ return if self._custom_field_localize_diff[name].empty?
174
+
175
+ self.send(name).all.each do |record|
176
+ updates = {}
177
+
178
+ # puts "[apply_custom_fields_localize_diff] processing: record #{record._id} / #{self._custom_field_localize_diff[name].inspect}" # DEBUG
179
+ self._custom_field_localize_diff[name].each do |changes|
180
+ if changes[:localized]
181
+ value = record.read_attribute(changes[:field].to_sym)
182
+ updates[changes[:field]] = { I18n.locale.to_s => value }
183
+ else
184
+ # the other way around
185
+ value = record.read_attribute(changes[:field].to_sym)
186
+ next if value.nil?
187
+ updates[changes[:field]] = value[I18n.locale.to_s]
188
+ end
189
+ end
190
+
191
+ next if updates.empty?
192
+
193
+ collection = self.send(name).collection
194
+ collection.update record.atomic_selector, { '$set' => updates }
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ module ClassMethods
201
+
202
+ # Determines if the relation is enhanced by the custom fields
203
+ #
204
+ # @example the Person class has somewhere in its code this: "custom_fields_for :addresses"
205
+ # Person.custom_fields_for?(:addresses)
206
+ #
207
+ # @param [ String, Symbol ] name The name of the relation.
208
+ #
209
+ # @return [ true, false ] True if enhanced, false if not.
210
+ #
211
+ def custom_fields_for?(name)
212
+ self._custom_fields_for.include?(name.to_s)
213
+ end
214
+
215
+ # Enhance a referenced collection OR the instance itself (by passing self) by providing methods to manage custom fields.
216
+ #
217
+ # @param [ String, Symbol ] name The name of the relation.
218
+ #
219
+ # @example
220
+ # class Company
221
+ # embeds_many :employees
222
+ # custom_fields_for :employees
223
+ # end
224
+ #
225
+ # class Employee
226
+ # embedded_in :company, :inverse_of => :employees
227
+ # field :name, String
228
+ # end
229
+ #
230
+ # company.employees_custom_fields.build :label => 'His/her position', :name => 'position', :kind => 'string'
231
+ # company.save
232
+ # company.employees.build :name => 'Michael Scott', :position => 'Regional manager'
233
+ #
234
+ def custom_fields_for(name)
235
+ self.declare_embedded_in_definition_in_custom_field(name)
236
+
237
+ # stores the relation name
238
+ self._custom_fields_for << name.to_s
239
+
240
+ self.extend_for_custom_fields(name)
241
+ end
242
+
243
+ protected
244
+
245
+ # Extends / Decorates the current class in order to be fully custom_fields compliant.
246
+ # it declares news fields, adds new callbacks, ...etc
247
+ #
248
+ # @param [ String, Symbol ] name The name of the relation.
249
+ #
250
+ def extend_for_custom_fields(name)
251
+ class_eval do
252
+ field :"#{name}_custom_fields_version", :type => Integer, :default => 0
253
+
254
+ embeds_many :"#{name}_custom_fields", :class_name => self.dynamic_custom_field_class_name(name) #, :cascade_callbacks => true # FIXME ?????
255
+
256
+ accepts_nested_attributes_for :"#{name}_custom_fields", :allow_destroy => true
257
+ end
258
+
259
+ class_eval <<-EOV
260
+ after_initialize :refresh_#{name}_metadata
261
+ before_update :bump_#{name}_custom_fields_version
262
+ before_update :collect_#{name}_custom_fields_diff
263
+ after_update :apply_#{name}_custom_fields_diff
264
+ after_update :apply_#{name}_custom_fields_localize_diff
265
+
266
+ def ordered_#{name}_custom_fields
267
+ self.ordered_custom_fields('#{name}')
268
+ end
269
+
270
+ protected
271
+
272
+ def refresh_#{name}_metadata
273
+ self.refresh_metadata_with_custom_fields('#{name}')
274
+ end
275
+
276
+ def bump_#{name}_custom_fields_version
277
+ self.bump_custom_fields_version('#{name}')
278
+ end
279
+
280
+ def collect_#{name}_custom_fields_diff
281
+ self.collect_custom_fields_diff(:#{name}, self.#{name}_custom_fields)
282
+ end
283
+
284
+ def apply_#{name}_custom_fields_diff
285
+ self.apply_custom_fields_diff(:#{name})
286
+ end
287
+
288
+ def apply_#{name}_custom_fields_localize_diff
289
+ self.apply_custom_fields_localize_diff(:#{name})
290
+ end
291
+
292
+ EOV
293
+ end
294
+
295
+ # Returns the class name of the custom field which is based both on the parent class name
296
+ # and the name of the relation in order to avoid name conflicts (with other classes)
297
+ #
298
+ # @param [ Metadata ] metadata The relation's old metadata.
299
+ #
300
+ # @return [ String ] The class name
301
+ #
302
+ def dynamic_custom_field_class_name(name)
303
+ "#{self.name}#{name.to_s.singularize.camelize}Field"
304
+ end
305
+
306
+ # An embedded relationship has to be defined on both side in order for it
307
+ # to work properly. But because custom_field can be embedded in different
308
+ # models that it's not aware of, we have to declare manually the definition
309
+ # once we know the target class.
310
+ #
311
+ # @param [ String, Symbol ] name The name of the relation.
312
+ #
313
+ # @return [ Field ] The new field class.
314
+ #
315
+ def declare_embedded_in_definition_in_custom_field(name)
316
+ klass_name = self.dynamic_custom_field_class_name(name).split('::').last # Use only the class, ignore the modules
317
+
318
+ source = self.parents.size > 1 ? self.parents.first : Object
319
+
320
+ unless source.const_defined?(klass_name)
321
+ (klass = Class.new(::CustomFields::Field)).class_eval <<-EOF
322
+ embedded_in :#{self.name.demodulize.underscore}, :inverse_of => :#{name}_custom_fields, :class_name => '#{self.name}'
323
+ EOF
324
+
325
+ source.const_set(klass_name, klass)
326
+ end
327
+ end
328
+
329
+ end
330
+
331
+ end
332
+
333
+ end