gnuside-custom_fields 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +70 -0
  4. data/config/locales/de.yml +15 -0
  5. data/config/locales/en.yml +21 -0
  6. data/config/locales/fr.yml +25 -0
  7. data/config/locales/pt-BR.yml +9 -0
  8. data/config/locales/ru.yml +15 -0
  9. data/lib/custom_fields/extensions/active_support.rb +28 -0
  10. data/lib/custom_fields/extensions/carrierwave.rb +25 -0
  11. data/lib/custom_fields/extensions/mongoid/document.rb +21 -0
  12. data/lib/custom_fields/extensions/mongoid/factory.rb +20 -0
  13. data/lib/custom_fields/extensions/mongoid/fields/i18n.rb +55 -0
  14. data/lib/custom_fields/extensions/mongoid/fields/localized.rb +39 -0
  15. data/lib/custom_fields/extensions/mongoid/fields.rb +31 -0
  16. data/lib/custom_fields/extensions/mongoid/relations/referenced/in.rb +22 -0
  17. data/lib/custom_fields/extensions/mongoid/relations/referenced/many.rb +34 -0
  18. data/lib/custom_fields/extensions/mongoid/validations/collection_size.rb +43 -0
  19. data/lib/custom_fields/extensions/mongoid/validations/macros.rb +25 -0
  20. data/lib/custom_fields/extensions/origin/smash.rb +33 -0
  21. data/lib/custom_fields/field.rb +106 -0
  22. data/lib/custom_fields/source.rb +347 -0
  23. data/lib/custom_fields/target.rb +99 -0
  24. data/lib/custom_fields/target_helpers.rb +192 -0
  25. data/lib/custom_fields/types/belongs_to.rb +65 -0
  26. data/lib/custom_fields/types/boolean.rb +55 -0
  27. data/lib/custom_fields/types/date.rb +97 -0
  28. data/lib/custom_fields/types/date_time.rb +97 -0
  29. data/lib/custom_fields/types/default.rb +103 -0
  30. data/lib/custom_fields/types/email.rb +60 -0
  31. data/lib/custom_fields/types/file.rb +74 -0
  32. data/lib/custom_fields/types/float.rb +52 -0
  33. data/lib/custom_fields/types/has_many.rb +74 -0
  34. data/lib/custom_fields/types/integer.rb +54 -0
  35. data/lib/custom_fields/types/many_to_many.rb +75 -0
  36. data/lib/custom_fields/types/money.rb +146 -0
  37. data/lib/custom_fields/types/relationship_default.rb +44 -0
  38. data/lib/custom_fields/types/select.rb +217 -0
  39. data/lib/custom_fields/types/string.rb +55 -0
  40. data/lib/custom_fields/types/tags.rb +35 -0
  41. data/lib/custom_fields/types/text.rb +65 -0
  42. data/lib/custom_fields/version.rb +6 -0
  43. data/lib/custom_fields.rb +74 -0
  44. metadata +244 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f86fd3a6c13622b1fe2e5bd94d46f9076554639d
4
+ data.tar.gz: d7b5676d0bf7cf6e845e1cf35cf4e0b67c60b4cb
5
+ SHA512:
6
+ metadata.gz: fa158bc35c5ee8b24870d941e6ebe07439a7e99ee1f4239bac8b8fb925aa12b20233d9b647906b93660f95a8a591c8263723bab6fc0a26d0e6d88bff64ff59d9
7
+ data.tar.gz: 17279f2aa0606472a81e468b01c39bffd77064faaf4e4e63a2c4e24aa4f275dfd22f82590b887d3c60ac9dd9b192526bb0b5eb716d359a7752d0a79b43fef772
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 [Didier Lafforgue]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,70 @@
1
+ "!https://secure.travis-ci.org/locomotivecms/custom_fields.png!":http://travis-ci.org/locomotivecms/custom_fields
2
+
3
+ h1. CustomFields
4
+
5
+ Manage custom fields to a mongoid document or a collection. This module is one of the core features we implemented in our custom cms, LocomotiveCMS.
6
+ Basically, its aim is to provide to editors a way to manage extra fields to a Mongoid document through, for instance, a web UI.
7
+
8
+ The main goals:
9
+
10
+ * offering a very secure way to add / edit / delete extra fields to a Mongoid document
11
+ * scoping the modifications added to a Mongoid document so that other documents of the same class won't be updated.
12
+
13
+ h2. Requirements
14
+
15
+ ActiveSupport 3.2.13, MongoDB 2.0 and Mongoid 3.1.3
16
+
17
+ h2. Examples
18
+
19
+ h3. On a has_many relationship
20
+
21
+ bc.. class Company
22
+ include CustomFields::Source
23
+
24
+ has_many :employees
25
+
26
+ custom_fields_for :employees
27
+ end
28
+
29
+ class Employee
30
+ include CustomFields::Target
31
+
32
+ field :name, String
33
+
34
+ belongs_to :company, :inverse_of => :employees
35
+ end
36
+
37
+ company = Company.new
38
+ company.employees_custom_fields.build :label => 'His/her position', :name => 'position', :type => 'string', :required => true
39
+
40
+ company.save
41
+
42
+ company.employees.build :name => 'Michael Scott', :position => 'Regional manager'
43
+
44
+ another_company = Company.new
45
+ employee = another_company.employees.build
46
+ employee.position # returns a "not defined method" error
47
+
48
+ h3. On the class itself
49
+
50
+ [IN PROGRESS]
51
+
52
+ bc.. class Company
53
+ custom_fields_for_itself
54
+ end
55
+
56
+ company = Company.new
57
+ company.self_metadata_custom_fields.build :label => 'Shipping Address', :name => 'address', :type => 'text'
58
+
59
+ company.save
60
+
61
+ company.self_metadata.address = '700 S Laflin, 60607 Chicago'
62
+
63
+ another_company = Company.new
64
+ other_company.self_metadata.address # returns a "not defined method" error
65
+
66
+ h2. Contact
67
+
68
+ Feel free to contact me at didier at nocoffee dot fr.
69
+
70
+ Copyright (c) 2013 NoCoffee, released under the MIT license
@@ -0,0 +1,15 @@
1
+ de:
2
+ custom_fields:
3
+ type:
4
+ string: Einfacher Text
5
+ text: Formatierter Text
6
+ select: Auswahl-Liste
7
+ boolean: Auswahl-Box
8
+ integer: Ganzzahl
9
+ float: Fließkommazahl
10
+ money: Währung
11
+ date: Datum
12
+ file: Datei
13
+ belongs_to: gehört zu
14
+ has_many: Hat viele
15
+ many_to_many: Hat und gehört zu vielen
@@ -0,0 +1,21 @@
1
+ en:
2
+ custom_fields:
3
+ type:
4
+ string: Simple Input
5
+ text: Text
6
+ select: Select
7
+ boolean: Checkbox
8
+ date: Date
9
+ file: File
10
+ integer: Integer
11
+ float: Float
12
+ money: Money
13
+ email: E-mail
14
+ tags: Tags
15
+ belongs_to: Belongs to
16
+ has_many: Has many
17
+ many_to_many: Many To Many
18
+
19
+ errors:
20
+ messages:
21
+ at_least_one_element: "must have at least one element"
@@ -0,0 +1,25 @@
1
+ fr:
2
+ custom_fields:
3
+ type:
4
+ string: Texte
5
+ text: Zone de texte
6
+ select: Liste déroulante
7
+ boolean: Case à cocher
8
+ date: Date
9
+ integer: Nombre
10
+ file: Fichier
11
+ integer: Entier
12
+ float: Décimal
13
+ money: Monnaie
14
+ tags: Libellés
15
+ belongs_to: Appartient à
16
+ has_many: A plusieurs
17
+ many_to_many: A plusieurs et appartient à
18
+ text_formatting:
19
+ none: Aucun
20
+ html: HTML
21
+
22
+ errors:
23
+ messages:
24
+ blank: "doit être rempli(e)"
25
+ at_least_one_element: "doit avoir au moins un élément"
@@ -0,0 +1,9 @@
1
+ pt-BR:
2
+ custom_fields:
3
+ kind:
4
+ string: Texto Simples
5
+ text: Texto
6
+ category: Caixa de Seleção
7
+ boolean: Checkbox
8
+ date: Data
9
+ file: Arquivo
@@ -0,0 +1,15 @@
1
+ ru:
2
+ custom_fields:
3
+ type:
4
+ string: Простая строка
5
+ text: Текст
6
+ select: Выбор
7
+ boolean: Чекбокс
8
+ date: Дата
9
+ file: Файл
10
+ integer: Целое число
11
+ float: Число с дробной частью
12
+ money: Деньги
13
+ belongs_to: Относится к
14
+ has_many: Имеет много
15
+ many_to_many: Многие ко многим
@@ -0,0 +1,28 @@
1
+ class String
2
+
3
+ def constantize_with_custom_fields
4
+ begin
5
+ constantize_without_custom_fields
6
+ rescue NameError => exception
7
+ # DEBUG: puts "constantizing #{self.inspect}"
8
+ # alright, does it look like a custom_fields dynamic klass ?
9
+ if self =~ /(.*)([0-9a-fA-F]{24})$/
10
+ base = $1.constantize
11
+ # we can know it for sure
12
+ if base.with_custom_fields?
13
+ relation = base.relations.values.detect { |metadata| metadata[:custom_fields_parent_klass] == true }
14
+
15
+ # load the class which holds the recipe to build the dynamic klass
16
+ if relation && parent_instance = relation.klass.find($2)
17
+ # DEBUG: puts "re-building #{self}"
18
+ return parent_instance.klass_with_custom_fields(relation.inverse_of)
19
+ end
20
+ end
21
+ end
22
+ # not a custom_fields dynamic klass or unable to re-build it
23
+ raise exception
24
+ end
25
+ end
26
+
27
+ alias_method_chain :constantize, :custom_fields
28
+ end
@@ -0,0 +1,25 @@
1
+ require 'carrierwave/mongoid'
2
+
3
+ module CarrierWave
4
+
5
+ module Mongoid
6
+
7
+ def mount_uploader_with_localization(column, uploader=nil, options={}, &block)
8
+ mount_uploader_without_localization(column, uploader, options, &block)
9
+
10
+ define_method(:read_uploader) do |name|
11
+ # puts "read_uploader #{name} / #{read_attribute(name.to_sym).inspect}" # DEBUG
12
+
13
+ value = read_attribute(name.to_sym)
14
+ unless value.nil?
15
+ self.class.fields[name.to_s].demongoize(value)
16
+ else
17
+ nil
18
+ end
19
+ end
20
+ end
21
+
22
+ alias_method_chain :mount_uploader, :localization
23
+ end
24
+
25
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+
4
+ module Document #:nodoc:
5
+
6
+ module ClassMethods #:nodoc:
7
+
8
+ # The mongoid default document returns always false.
9
+ # The documents with custom fields return true.
10
+ #
11
+ # @return [ Boolean ] False
12
+ #
13
+ def with_custom_fields?
14
+ false
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+
4
+ # Instantiates documents that came from the database.
5
+ module Factory
6
+
7
+ def from_db_with_custom_fields(klass, attributes = {}, criteria_instance_id = nil)
8
+ if klass.with_custom_fields?
9
+ klass.klass_with_custom_fields(attributes['custom_fields_recipe'])
10
+ end
11
+ from_db_without_custom_fields(klass, attributes, criteria_instance_id)
12
+ end
13
+
14
+ # equivalent for "alias_method_chain :from_db, :custom_fields"
15
+ alias_method :from_db_without_custom_fields, :from_db unless method_defined?(:from_db_without_custom_fields)
16
+ alias_method :from_db, :from_db_with_custom_fields
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,55 @@
1
+ module Mongoid #:nodoc
2
+
3
+ # This module defines behaviour for fields.
4
+ module Fields
5
+
6
+ class I18n
7
+
8
+ attr_accessor :locale, :fallbacks
9
+
10
+ def self.instance
11
+ Thread.current[:mongoid_i18n] ||= Mongoid::Fields::I18n.new
12
+ end
13
+
14
+ def self.locale
15
+ self.instance.locale || ::I18n.locale
16
+ end
17
+
18
+ def self.locale=(value)
19
+ self.instance.locale = value.to_sym rescue nil
20
+ end
21
+
22
+ def self.fallbacks
23
+ if ::I18n.respond_to?(:fallbacks)
24
+ ::I18n.fallbacks
25
+ elsif !self.instance.fallbacks.blank?
26
+ self.instance.fallbacks
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def self.fallbacks_for(locale, fallbacks)
33
+ self.instance.fallbacks ||= {}
34
+ self.instance.fallbacks[locale.to_sym] = fallbacks
35
+ end
36
+
37
+ def self.fallbacks?
38
+ ::I18n.respond_to?(:fallbacks) || !self.instance.fallbacks.blank?
39
+ end
40
+
41
+ def self.with_locale(new_locale = nil)
42
+ if new_locale
43
+ current_locale = self.locale
44
+ self.locale = new_locale
45
+ end
46
+ yield
47
+ ensure
48
+ self.locale = current_locale if new_locale
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Fields #:nodoc:
4
+
5
+ # The behaviour of the Localized fields in the custom fields gem is different
6
+ # because we do not rely on I18n directly but on a slight version Mongoid::Fields::I18n.
7
+ # The main reason is only practical to handle the following case:
8
+ # -> Back-office in English and editing content in French.
9
+ #
10
+ # TODO: use this gem instead https://github.com/simi/mongoid-localizer
11
+ #
12
+ class Localized < Standard
13
+
14
+ def mongoize(object)
15
+ { locale.to_s => type.mongoize(object) }
16
+ end
17
+
18
+ private
19
+
20
+ def lookup(object)
21
+ if !object.respond_to?(:keys) # if no translation hash is given, we return the object itself
22
+ object
23
+ elsif object.has_key?(locale.to_s)
24
+ object[locale.to_s]
25
+ elsif I18n.fallbacks?
26
+ object[I18n.fallbacks[locale].map(&:to_s).find { |loc| !object[loc].nil? }]
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def locale
33
+ # be careful, it does not return ::I18n.locale
34
+ I18n.locale
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ module Mongoid #:nodoc
2
+
3
+ # This module defines behaviour for fields.
4
+ module Fields
5
+
6
+ module ClassMethods
7
+
8
+ # Replace a field with a new type.
9
+ #
10
+ # @example Replace the field.
11
+ # Model.replace_field("_id", String)
12
+ #
13
+ # @param [ String ] name The name of the field.
14
+ # @param [ Class ] type The new type of field.
15
+ # @param [ Boolean ] localize The option to localize or not the field.
16
+ #
17
+ # @return [ Serializable ] The new field.
18
+ #
19
+ # @since 2.1.0
20
+ def replace_field(name, type, localize = false)
21
+ # puts "fields[#{name}] = #{fields[name.to_s].inspect} / #{fields.keys.inspect}" # DEBUG
22
+ #attribute_names.delete_one(name)
23
+ remove_defaults(name)
24
+ add_field(name, fields[name.to_s].options.merge(type: type, localize: localize))
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+ module Referenced #:nodoc:
5
+
6
+ class In < Relations::One
7
+
8
+ class << self
9
+
10
+ def valid_options_with_parent_class
11
+ valid_options_without_parent_class.push :custom_fields_parent_klass
12
+ end
13
+
14
+ alias_method_chain :valid_options, :parent_class
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Relations #:nodoc:
4
+ module Referenced #:nodoc:
5
+
6
+ # This class defines the behaviour for all relations that are a
7
+ # one-to-many between documents in different collections.
8
+ class Many < Relations::Many
9
+
10
+ def build_with_custom_fields(attributes = {}, options = {}, type = nil)
11
+ if base.respond_to?(:custom_fields_for?) && base.custom_fields_for?(metadata.name)
12
+ # all the information about how to build the custom class are stored here
13
+ recipe = base.custom_fields_recipe_for(metadata.name)
14
+
15
+ attributes ||= {}
16
+
17
+ attributes.merge!(custom_fields_recipe: recipe)
18
+
19
+ # build the class with custom_fields for the first time
20
+ type = metadata.klass.klass_with_custom_fields(recipe)
21
+ end
22
+ build_without_custom_fields(attributes, options, type)
23
+
24
+ end
25
+
26
+ alias_method_chain :build, :custom_fields
27
+
28
+ # new should point to the new build method
29
+ alias :new :build_with_custom_fields
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Validations
4
+
5
+ # Validates that the specified collections do or do not match a certain
6
+ # size.
7
+ #
8
+ # @example Set up the collection size validator.
9
+ #
10
+ # class Person
11
+ # include Mongoid::Document
12
+ # has_many :addresses
13
+ #
14
+ # validates_collection_size_of :addresses, in: 1..10
15
+ # end
16
+ class CollectionSizeValidator < LengthValidator
17
+
18
+ def validate_each_with_collection(record, attribute, value)
19
+ value = collection_to_size(record, attribute)
20
+
21
+ self.validate_each_without_collection(record, attribute, value)
22
+ end
23
+
24
+ alias_method_chain :validate_each, :collection
25
+
26
+ private
27
+
28
+ def collection_to_size(record, attribute)
29
+ relation = record.relations[attribute.to_s]
30
+
31
+ source = case relation.macro
32
+ when :embeds_many, :has_many
33
+ record.send(attribute)
34
+ when :has_and_belongs_to_many
35
+ record.send(relation.key.to_sym)
36
+ end
37
+
38
+ OpenStruct.new(length: source.try(:size) || 0)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module Mongoid
2
+ module Validations
3
+ module Macros
4
+ extend ActiveSupport::Concern
5
+
6
+ # Validates the size of a collection.
7
+ #
8
+ # @example
9
+ # class Person
10
+ # include Mongoid::Document
11
+ # has_many :addresses
12
+ #
13
+ # validates_collection_size_of :addresses, minimum: 1
14
+ # end
15
+ #
16
+ # @param [ Array ] args The names of the fields to validate.
17
+ #
18
+ # @since 2.4.0
19
+ def validates_collection_size_of(*args)
20
+ validates_with(Mongoid::Validations::CollectionSizeValidator, _merge_attributes(args))
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ module Origin
3
+
4
+ # This is a smart hash for use with options and selectors.
5
+ class Smash < Hash
6
+
7
+ private
8
+
9
+ # Get the normalized value for the key. If localization is in play the
10
+ # current locale will be appended to the key in MongoDB dot notation.
11
+ #
12
+ # FIXME (Did).
13
+ # This version DOES NOT USE ::I18n.locale directly.
14
+ # See the localized.rb file for more explanation.
15
+ #
16
+ # @api private
17
+ #
18
+ # @example Get the normalized key name.
19
+ # smash.normalized_key("field", serializer)
20
+ #
21
+ # @param [ String ] name The name of the field.
22
+ # @param [ Object ] serializer The optional field serializer.
23
+ #
24
+ # @return [ String ] The normalized key.
25
+ #
26
+ # @since 1.0.0
27
+ def normalized_key(name, serializer)
28
+ # serializer && serializer.localized? ? "#{name}.#{::I18n.locale}" : name
29
+ serializer && serializer.localized? ? "#{name}.#{::Mongoid::Fields::I18n.locale}" : name
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,106 @@
1
+ module CustomFields
2
+
3
+ class Field
4
+
5
+ include ::Mongoid::Document
6
+ include ::Mongoid::Timestamps
7
+
8
+ AVAILABLE_TYPES = %w(default string text email date date_time boolean file select float integer
9
+ money tags relationship_default belongs_to has_many many_to_many)
10
+
11
+ ## types ##
12
+ AVAILABLE_TYPES.each do |type|
13
+ include "CustomFields::Types::#{type.camelize}::Field".constantize
14
+ end
15
+
16
+ ## fields ##
17
+ field :label
18
+ field :name
19
+ field :type
20
+ field :hint
21
+ field :position, type: ::Integer, default: 0
22
+ field :required, type: ::Boolean, default: false
23
+ field :unique, type: ::Boolean, default: false
24
+ field :localized, type: ::Boolean, default: false
25
+
26
+ ## validations ##
27
+ validates_presence_of :label, :type
28
+ validates_exclusion_of :name, in: lambda { |f| CustomFields.options[:reserved_names].map(&:to_s) }
29
+ validates_inclusion_of :type, in: AVAILABLE_TYPES, allow_blank: true
30
+ validates_format_of :name, with: /^[a-z]([A-Za-z0-9_]+)?$/
31
+ validate :uniqueness_of_label_and_name
32
+
33
+ ## callbacks ##
34
+ before_validation :set_name
35
+
36
+ ## methods ##
37
+
38
+ # Builds the mongodb updates based on
39
+ # the new state of the field.
40
+ # Call a different method if the field has a different behaviour.
41
+ #
42
+ # @param [ Hash ] memo Store the updates
43
+ #
44
+ # @return [ Hash ] The memo object upgraded
45
+ #
46
+ def collect_diff(memo)
47
+ method_name = :"collect_#{self.type}_diff"
48
+
49
+ if self.respond_to?(method_name)
50
+ self.send(method_name, memo)
51
+ else
52
+ collect_default_diff(memo)
53
+ end
54
+ end
55
+
56
+ # Returns the information (name, type, required, ...etc) needed to build
57
+ # the custom class.
58
+ # That will be stored into the target instance.
59
+ #
60
+ # @return [ Hash ] The hash
61
+ #
62
+ def to_recipe
63
+ method_name = :"#{self.type}_to_recipe"
64
+ custom_to_recipe = self.send(method_name) rescue {}
65
+
66
+ { 'name' => self.name,
67
+ 'type' => self.type,
68
+ 'required' => self.required?,
69
+ 'unique' => self.unique?,
70
+ 'localized' => self.localized? }.merge(custom_to_recipe)
71
+ end
72
+
73
+ def as_json(options = {})
74
+ method_name = :"#{self.type}_as_json"
75
+ custom_as_json = self.send(method_name) rescue {}
76
+
77
+ super(options).merge(custom_as_json)
78
+ end
79
+
80
+ protected
81
+
82
+ def uniqueness_of_label_and_name
83
+ if self.siblings.any? { |f| f.label == self.label && f._id != self._id }
84
+ self.errors.add(:label, :taken)
85
+ end
86
+
87
+ if self.siblings.any? { |f| f.name == self.name && f._id != self._id }
88
+ self.errors.add(:name, :taken)
89
+ end
90
+ end
91
+
92
+ def set_name
93
+ return if self.label.blank? && self.name.blank?
94
+
95
+ if self.name.blank?
96
+ self.name = self.label.parameterize('_').gsub('-', '_').downcase
97
+ end
98
+ end
99
+
100
+ def siblings
101
+ self._parent.send(self.metadata.name)
102
+ end
103
+
104
+ end
105
+
106
+ end