custom_fields 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README.textile +4 -4
  3. data/config/locales/de.yml +11 -5
  4. data/config/locales/en.yml +4 -0
  5. data/config/locales/fr.yml +4 -0
  6. data/config/locales/ru.yml +15 -0
  7. data/lib/custom_fields.rb +37 -27
  8. data/lib/custom_fields/extensions/active_support.rb +1 -1
  9. data/lib/custom_fields/extensions/carrierwave.rb +2 -2
  10. data/lib/custom_fields/extensions/mongoid/factory.rb +3 -3
  11. data/lib/custom_fields/extensions/mongoid/fields.rb +4 -3
  12. data/lib/custom_fields/extensions/mongoid/fields/localized.rb +39 -0
  13. data/lib/custom_fields/extensions/mongoid/relations/referenced/in.rb +1 -1
  14. data/lib/custom_fields/extensions/mongoid/relations/referenced/many.rb +3 -3
  15. data/lib/custom_fields/field.rb +14 -8
  16. data/lib/custom_fields/source.rb +23 -14
  17. data/lib/custom_fields/target.rb +16 -8
  18. data/lib/custom_fields/target_helpers.rb +6 -6
  19. data/lib/custom_fields/types/belongs_to.rb +4 -4
  20. data/lib/custom_fields/types/boolean.rb +1 -1
  21. data/lib/custom_fields/types/date.rb +2 -2
  22. data/lib/custom_fields/types/default.rb +5 -6
  23. data/lib/custom_fields/types/email.rb +60 -0
  24. data/lib/custom_fields/types/file.rb +2 -2
  25. data/lib/custom_fields/types/float.rb +52 -0
  26. data/lib/custom_fields/types/has_many.rb +6 -8
  27. data/lib/custom_fields/types/integer.rb +54 -0
  28. data/lib/custom_fields/types/many_to_many.rb +3 -3
  29. data/lib/custom_fields/types/money.rb +146 -0
  30. data/lib/custom_fields/types/relationship_default.rb +2 -2
  31. data/lib/custom_fields/types/select.rb +16 -13
  32. data/lib/custom_fields/types/tags.rb +35 -0
  33. data/lib/custom_fields/types/text.rb +1 -1
  34. data/lib/custom_fields/version.rb +1 -1
  35. metadata +65 -76
  36. data/init.rb +0 -2
  37. data/lib/custom_fields/extensions/mongoid/fields/internal/localized.rb +0 -86
@@ -7,15 +7,15 @@ module CustomFields
7
7
  included do
8
8
 
9
9
  ## types ##
10
- %w(default string text date boolean file select belongs_to has_many many_to_many).each do |type|
11
- include "CustomFields::Types::#{type.classify}::Target".constantize
10
+ %w(default string text email date boolean file select float integer money
11
+ belongs_to has_many many_to_many tags).each do |type|
12
+ include "CustomFields::Types::#{type.camelize}::Target".constantize
12
13
  end
13
14
 
14
15
  include ::CustomFields::TargetHelpers
15
16
 
16
17
  ## fields ##
17
- field :custom_fields_recipe, :type => Hash
18
-
18
+ field :custom_fields_recipe, type: Hash
19
19
  end
20
20
 
21
21
  module ClassMethods
@@ -33,13 +33,11 @@ module CustomFields
33
33
  #
34
34
  # @param [ Hash ] recipe The recipe describing the fields to add
35
35
  #
36
- # @return [ Class] the anonymous custom klass
36
+ # @return [ Class ] the anonymous custom klass
37
37
  #
38
38
  def build_klass_with_custom_fields(recipe)
39
39
  name = recipe['name']
40
-
41
40
  # puts "CREATING #{name}, #{recipe.inspect}" # DEBUG
42
-
43
41
  parent.const_set(name, Class.new(self)).tap do |klass|
44
42
  klass.cattr_accessor :version
45
43
 
@@ -52,6 +50,16 @@ module CustomFields
52
50
  recipe['rules'].each do |rule|
53
51
  self.send(:"apply_#{rule['type']}_custom_field", klass, rule)
54
52
  end
53
+ recipe_model_name = recipe['model_name']
54
+ model_name = Proc.new do
55
+ if recipe_model_name.is_a?(ActiveModel::Name)
56
+ recipe_model_name
57
+ else
58
+ recipe_model_name.constantize.model_name
59
+ end
60
+ end
61
+ klass.send :define_method, :model_name, model_name
62
+ klass.send :define_singleton_method, :model_name, model_name
55
63
  end
56
64
  end
57
65
 
@@ -86,4 +94,4 @@ module CustomFields
86
94
 
87
95
  end
88
96
 
89
- end
97
+ end
@@ -36,7 +36,7 @@ module CustomFields
36
36
  def custom_fields_safe_setters
37
37
  self.custom_fields_recipe['rules'].map do |rule|
38
38
  case rule['type'].to_sym
39
- when :date then "formatted_#{rule['name']}"
39
+ when :date, :money then "formatted_#{rule['name']}"
40
40
  when :file then [rule['name'], "remove_#{rule['name']}"]
41
41
  when :select, :belongs_to then ["#{rule['name']}_id", "position_in_#{rule['name']}"]
42
42
  when :has_many, :many_to_many then nil
@@ -164,10 +164,10 @@ module CustomFields
164
164
  #
165
165
  def custom_fields_getters_for(name, type)
166
166
  case type.to_sym
167
- when :select then [name, "#{name}_id"]
168
- when :date then "formatted_#{name}"
169
- when :file then "#{name}_url"
170
- when :belongs_to then "#{name}_id"
167
+ when :select then [name, "#{name}_id"]
168
+ when :date, :money then "formatted_#{name}"
169
+ when :file then "#{name}_url"
170
+ when :belongs_to then "#{name}_id"
171
171
  else
172
172
  name
173
173
  end
@@ -189,4 +189,4 @@ module CustomFields
189
189
 
190
190
  end
191
191
 
192
- end
192
+ end
@@ -34,15 +34,15 @@ module CustomFields
34
34
  # @param [ Hash ] rule It contains the name of the field and if it is required or not
35
35
  #
36
36
  def apply_belongs_to_custom_field(klass, rule)
37
- # puts "#{klass.inspect}.belongs_to #{rule['name'].inspect}, :class_name => #{rule['class_name'].inspect}" # DEBUG
37
+ # puts "#{klass.inspect}.belongs_to #{rule['name'].inspect}, class_name: #{rule['class_name'].inspect}" # DEBUG
38
38
 
39
39
  position_name = "position_in_#{rule['name'].underscore}"
40
40
 
41
41
  # puts "#{klass.inspect}.field :#{position_name}" # DEBUG
42
42
 
43
- klass.field position_name, :type => Integer, :default => 0
43
+ klass.field position_name, type: ::Integer, default: 0
44
44
 
45
- klass.belongs_to rule['name'].to_sym, :class_name => rule['class_name']
45
+ klass.belongs_to rule['name'].to_sym, class_name: rule['class_name']
46
46
 
47
47
  if rule['required']
48
48
  klass.validates_presence_of rule['name'].to_sym
@@ -62,4 +62,4 @@ module CustomFields
62
62
 
63
63
  end
64
64
 
65
- end
65
+ end
@@ -18,7 +18,7 @@ module CustomFields
18
18
  # @param [ Hash ] rule It contains the name of the field.
19
19
  #
20
20
  def apply_boolean_custom_field(klass, rule)
21
- klass.field rule['name'], :type => ::Boolean, :localize => rule['localized'] || false, :default => false
21
+ klass.field rule['name'], type: ::Boolean, localize: rule['localized'] || false, default: false
22
22
  end
23
23
 
24
24
  # Build a hash storing the boolean value (true / false) for
@@ -20,7 +20,7 @@ module CustomFields
20
20
  def apply_date_custom_field(klass, rule)
21
21
  name = rule['name']
22
22
 
23
- klass.field name, :type => ::Date, :localize => rule['localized'] || false
23
+ klass.field name, type: ::Date, localize: rule['localized'] || false
24
24
 
25
25
  # other methods
26
26
  klass.send(:define_method, :"formatted_#{name}") { _get_formatted_date(name) }
@@ -90,4 +90,4 @@ module CustomFields
90
90
 
91
91
  end
92
92
 
93
- end
93
+ end
@@ -19,8 +19,8 @@ module CustomFields
19
19
  if self.destroyed?
20
20
  memo['$unset'][self.name] = 1
21
21
  elsif self.changed?
22
- if self.changes.key?(:name)
23
- old_name, new_name = self.changes[:name]
22
+ if self.changes.key?('name')
23
+ old_name, new_name = self.changes['name']
24
24
  memo['$rename'][old_name] = new_name
25
25
  end
26
26
  end
@@ -47,11 +47,10 @@ module CustomFields
47
47
  # @param [ Hash ] rule It contains the name of the field and if it is required or not
48
48
  #
49
49
  def apply_custom_field(klass, rule)
50
- klass.field rule['name'], :localize => rule['localized'] || false
50
+ klass.field rule['name'], localize: rule['localized'] || false
51
51
 
52
- if rule['required']
53
- klass.validates_presence_of rule['name']
54
- end
52
+ klass.validates_presence_of rule['name'] if rule['required']
53
+ klass.validates_uniqueness_of rule['name'], scope: :_type if rule['unique']
55
54
  end
56
55
 
57
56
  # Build a hash storing the formatted (or not) values
@@ -0,0 +1,60 @@
1
+ module CustomFields
2
+
3
+ module Types
4
+
5
+ module Email
6
+
7
+ module Field; end
8
+
9
+ module Target
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ module ClassMethods
14
+
15
+ # Add a string field
16
+ #
17
+ # @param [ Class ] klass The class to modify
18
+ # @param [ Hash ] rule It contains the name of the field and if it is required or not
19
+ #
20
+ def apply_email_custom_field(klass, rule)
21
+ name = rule['name']
22
+
23
+ klass.field name, type: ::String, localize: rule['localized'] || false
24
+ klass.validates_presence_of name if rule['required']
25
+ klass.validates_format_of name, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/, allow_blank: !rule['required']
26
+ klass.validates_uniqueness_of rule['name'], scope: :_type if rule['unique']
27
+ end
28
+
29
+ # Build a hash storing the raw value for
30
+ # a string custom field of an instance.
31
+ #
32
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
33
+ # @param [ String ] name The name of the string custom field
34
+ #
35
+ # @return [ Hash ] field name => raw value
36
+ #
37
+ def email_attribute_get(instance, name)
38
+ self.default_attribute_get(instance, name)
39
+ end
40
+
41
+ # Set the value for the instance and the string field specified by
42
+ # the 2 params.
43
+ #
44
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
45
+ # @param [ String ] name The name of the string custom field
46
+ # @param [ Hash ] attributes The attributes used to fetch the values
47
+ #
48
+ def email_attribute_set(instance, name, attributes)
49
+ self.default_attribute_set(instance, name, attributes)
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -39,7 +39,7 @@ module CustomFields
39
39
  # @return [ Hash ] field name => url or empty hash if no file
40
40
  #
41
41
  def file_attribute_get(instance, name)
42
- if instance.send(:"#{name}?")
42
+ if instance.send(:"#{name}?") #"
43
43
  value = instance.send(name.to_sym).url
44
44
  { name => value, "#{name}_url" => value }
45
45
  else
@@ -71,4 +71,4 @@ module CustomFields
71
71
 
72
72
  end
73
73
 
74
- end
74
+ end
@@ -0,0 +1,52 @@
1
+ module CustomFields
2
+ module Types
3
+ module Float
4
+
5
+ module Field; end
6
+
7
+ module Target
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ # Add a string field
13
+ #
14
+ # @param [ Class ] klass The class to modify
15
+ # @param [ Hash ] rule It contains the name of the field and if it is required or not
16
+ #
17
+ def apply_float_custom_field(klass, rule)
18
+ klass.field rule['name'], type: ::Float, localize: rule['localized'] || false
19
+ klass.validates_presence_of rule['name'] if rule['required']
20
+ klass.validates rule['name'], numericality: true, if: ->(x){ rule['required'] }
21
+ end
22
+
23
+ # Build a hash storing the raw value for
24
+ # a string custom field of an instance.
25
+ #
26
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
27
+ # @param [ String ] name The name of the string custom field
28
+ #
29
+ # @return [ Hash ] field name => raw value
30
+ #
31
+ def float_attribute_get(instance, name)
32
+ self.default_attribute_get(instance, name)
33
+ end
34
+
35
+ # Set the value for the instance and the string field specified by
36
+ # the 2 params.
37
+ #
38
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
39
+ # @param [ String ] name The name of the string custom field
40
+ # @param [ Hash ] attributes The attributes used to fetch the values
41
+ #
42
+ def float_attribute_set(instance, name, attributes)
43
+ self.default_attribute_set(instance, name, attributes)
44
+ end
45
+
46
+ end # ClassMethods
47
+
48
+ end # Target
49
+
50
+ end # Float
51
+ end # Types
52
+ end # CustomFields
@@ -34,14 +34,13 @@ module CustomFields
34
34
  # @param [ Hash ] rule It contains the name of the relation and if it is required or not
35
35
  #
36
36
  def apply_has_many_custom_field(klass, rule)
37
- # puts "#{klass.inspect}.has_many #{rule['name'].inspect}, :class_name => #{rule['class_name'].inspect}, :inverse_of => #{rule['inverse_of']}" # DEBUG
38
-
37
+ # puts "#{klass.inspect}.has_many #{rule['name'].inspect}, class_name: #{rule['class_name'].inspect}, inverse_of: #{rule['inverse_of']}" # DEBUG
39
38
  position_name = "position_in_#{rule['inverse_of']}"
40
39
 
41
40
  _order_by = rule['order_by'] || position_name.to_sym.asc
42
41
  _inverse_of = rule['inverse_of'].blank? ? nil : rule['inverse_of'] # an empty String can cause weird behaviours
43
42
 
44
- klass.has_many rule['name'], :class_name => rule['class_name'], :inverse_of => _inverse_of, :order => _order_by do
43
+ klass.has_many rule['name'], class_name: rule['class_name'], inverse_of: _inverse_of, order: _order_by do
45
44
 
46
45
  def filtered(conditions = {}, order_by = nil)
47
46
  list = conditions.empty? ? self : self.where(conditions)
@@ -49,8 +48,7 @@ module CustomFields
49
48
  if order_by
50
49
  list.order_by(order_by)
51
50
  else
52
- # calling all on a has_many relationship makes us lose the default order_by (mongoid bug ?)
53
- list.order(metadata.order)
51
+ list.order_by(metadata.order)
54
52
  end
55
53
  end
56
54
 
@@ -58,10 +56,10 @@ module CustomFields
58
56
 
59
57
  end
60
58
 
61
- klass.accepts_nested_attributes_for rule['name'], :allow_destroy => true
59
+ klass.accepts_nested_attributes_for rule['name'], allow_destroy: true
62
60
 
63
61
  if rule['required']
64
- klass.validates_length_of rule['name'], :minimum => 1
62
+ klass.validates_length_of rule['name'], minimum: 1
65
63
  end
66
64
  end
67
65
 
@@ -73,4 +71,4 @@ module CustomFields
73
71
 
74
72
  end
75
73
 
76
- end
74
+ end
@@ -0,0 +1,54 @@
1
+ module CustomFields
2
+ module Types
3
+ module Integer
4
+
5
+ module Field; end
6
+
7
+ module Target
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ # Add a integer field
13
+ #
14
+ # @param [ Class ] klass The class to modify
15
+ # @param [ Hash ] rule It contains the name of the field and if it is required or not
16
+ #
17
+ def apply_integer_custom_field(klass, rule)
18
+ name = rule['name']
19
+
20
+ klass.field name, type: ::Integer, localize: rule['localized'] || false
21
+ klass.validates_presence_of name if rule['required']
22
+ klass.validates name, numericality: { only_integer: true }, if: ->(x){ rule['required'] }
23
+ end
24
+
25
+ # Build a hash storing the raw value for
26
+ # a string custom field of an instance.
27
+ #
28
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
29
+ # @param [ String ] name The name of the string custom field
30
+ #
31
+ # @return [ Hash ] field name => raw value
32
+ #
33
+ def integer_attribute_get(instance, name)
34
+ self.default_attribute_get(instance, name)
35
+ end
36
+
37
+ # Set the value for the instance and the string field specified by
38
+ # the 2 params.
39
+ #
40
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
41
+ # @param [ String ] name The name of the string custom field
42
+ # @param [ Hash ] attributes The attributes used to fetch the values
43
+ #
44
+ def integer_attribute_set(instance, name, attributes)
45
+ self.default_attribute_set(instance, name, attributes)
46
+ end
47
+
48
+ end # ClassMethods
49
+
50
+ end # Target
51
+
52
+ end # Integer
53
+ end # Types
54
+ end # CustomFields
@@ -34,9 +34,9 @@ module CustomFields
34
34
  # @param [ Hash ] rule It contains the name of the relation and if it is required or not
35
35
  #
36
36
  def apply_many_to_many_custom_field(klass, rule)
37
- # puts "#{klass.inspect}.many_to_many #{rule['name'].inspect}, :class_name => #{rule['class_name'].inspect} / #{rule['order_by']}" # DEBUG
37
+ # puts "#{klass.inspect}.many_to_many #{rule['name'].inspect}, class_name: #{rule['class_name'].inspect} / #{rule['order_by']}" # DEBUG
38
38
 
39
- klass.has_and_belongs_to_many rule['name'], :class_name => rule['class_name'], :inverse_of => rule['inverse_of'], :order => rule['order_by'] do
39
+ klass.has_and_belongs_to_many rule['name'], class_name: rule['class_name'], inverse_of: rule['inverse_of'], order: rule['order_by'] do
40
40
 
41
41
  def filtered(conditions = {}, order_by = nil)
42
42
  list = conditions.empty? ? self : self.where(conditions)
@@ -56,7 +56,7 @@ module CustomFields
56
56
  end
57
57
 
58
58
  if rule['required']
59
- klass.validates_length_of rule['name'], :minimum => 1
59
+ klass.validates_length_of rule['name'], minimum: 1
60
60
  end
61
61
  end
62
62
 
@@ -0,0 +1,146 @@
1
+ module CustomFields
2
+ module Types
3
+ module Money
4
+
5
+ module Field
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # allow_currency_from_symbol means that the user is able
10
+ # to provide another currency instead of the the default
11
+ # e.g. default is 'EUR' and User sets '100.11 USD'
12
+ field :default_currency
13
+ field :allow_currency_from_symbol, type: ::Boolean, default: false
14
+
15
+ before_validation :set_default
16
+
17
+ protected
18
+
19
+ def set_default
20
+ self.default_currency ||= CustomFields.options[:default_currency]
21
+ end
22
+
23
+ def check_currency
24
+ ::Money::Currency.find( self.default_currency )
25
+ end
26
+
27
+ end # included
28
+
29
+ def money_to_recipe
30
+ { 'default_currency' => self.default_currency,
31
+ 'allow_currency_from_symbol' => self.allow_currency_from_symbol }
32
+ end
33
+
34
+ def money_as_json(options = {})
35
+ money_to_recipe
36
+ end
37
+
38
+ end # module Field
39
+
40
+
41
+ module Target
42
+ extend ActiveSupport::Concern
43
+
44
+ module ClassMethods
45
+
46
+ # Adds a Money field
47
+ #
48
+ # uses the money gem (https://github.com/RubyMoney/money)
49
+ #
50
+ # @param [ Class ] klass The class to modify
51
+ # @param [ Hash ] rule It contains the name of the field and if it is required or not
52
+ #
53
+ # if allow_currency_from_symbol is set the formatted_name_field will return the amount
54
+ # and the currency
55
+
56
+ def apply_money_custom_field(klass, rule)
57
+
58
+ # the field names
59
+ name = rule['name']
60
+ names = {
61
+ name: name.to_sym,
62
+ cents_field: "#{name}_cents".to_sym,
63
+ currency_field: "#{name}_currency".to_sym,
64
+ formatted_name_field: "formatted_#{name}".to_sym,
65
+ allow_currency_from_symbol: "#{name}_allow_currency_from_symbol".to_sym,
66
+ default_currency: "#{name}_default_currency".to_sym
67
+ }
68
+
69
+ # fields
70
+ klass.field names[:cents_field], type: ::Integer, localize: false
71
+ klass.field names[:currency_field], type: ::String, localize: false
72
+
73
+ # getters and setters
74
+ klass.send( :define_method, name ) { _get_money(names) }
75
+ klass.send( :define_method, :"#{name}=" ) { |value| _set_money(value, names) }
76
+
77
+ klass.send( :define_method, names[:formatted_name_field] ) { _get_formatted_money(names) }
78
+ klass.send( :define_method, :"#{names[:formatted_name_field]}=" ) { |value| _set_money(value, names) }
79
+
80
+ klass.send( :define_method, names[:allow_currency_from_symbol] ) { rule['allow_currency_from_symbol'] }
81
+ klass.send( :define_method, names[:default_currency] ) { rule['default_currency'] }
82
+
83
+ # validations
84
+ klass.validate { _check_money( names ) } if rule['required']
85
+ klass.validates_presence_of( names[:cents_field], names[:currency_field] ) if rule['required']
86
+ klass.validates_numericality_of names[:cents_field], only_integer: true, if: names[:cents_field]
87
+
88
+ end
89
+
90
+ def money_attribute_get(instance, name)
91
+ if value = instance.send(:"formatted_#{name}")
92
+ { name => value, "formatted_#{name}" => value }
93
+ else
94
+ {}
95
+ end
96
+ end
97
+
98
+ def money_attribute_set(instance, name, attributes)
99
+ return unless attributes.key?(name) || attributes.key?("formatted_#{name}")
100
+ value = attributes[name] || attributes["formatted_#{name}"]
101
+ instance.send(:"formatted_#{name}=", value)
102
+ end
103
+
104
+
105
+ end
106
+
107
+ protected
108
+
109
+ def _set_money_defaults( names )
110
+ ::Money.assume_from_symbol = self.send( names[:allow_currency_from_symbol] )
111
+ ::Money.default_currency = self.send( names[:default_currency] )
112
+ end
113
+
114
+ def _get_money( names )
115
+ _set_money_defaults( names )
116
+ ::Money.new( self.read_attribute( names[:cents_field] ), self.read_attribute( names[:currency_field] ) || ::Money.default_currency )
117
+ end
118
+
119
+ def _check_money( names )
120
+ if [nil, ''].include? self.read_attribute.names[:cents_field]
121
+ raise ArgumentError.new 'Unrecognized amount'
122
+ end
123
+ _get_money( names )
124
+ rescue
125
+ self.errors.add( names[:name], "#{$!}" )
126
+ false
127
+ end
128
+
129
+ def _set_money( _money, names )
130
+ return if _money.blank?
131
+ _set_money_defaults( names )
132
+ money = _money.kind_of?( Money ) ? _money : ::Money.parse( _money )
133
+ self.write_attribute( names[:cents_field], money.cents )
134
+ self.write_attribute( names[:currency_field], money.currency.iso_code )
135
+ rescue
136
+ self.errors.add( names[:name], "#{$!}" )
137
+ end
138
+
139
+ def _get_formatted_money( names )
140
+ _get_money( names ).format( symbol: self.send( names[:allow_currency_from_symbol] ), no_cents_if_whole: true ) rescue nil
141
+ end
142
+
143
+ end
144
+ end
145
+ end
146
+ end