gnuside-custom_fields 2.3.1

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 (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
@@ -0,0 +1,75 @@
1
+ module CustomFields
2
+
3
+ module Types
4
+
5
+ module ManyToMany
6
+
7
+ module Field
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+
13
+ def many_to_many_to_recipe
14
+ { 'class_name' => self.class_name, 'inverse_of' => self.inverse_of, 'order_by' => self.order_by }
15
+ end
16
+
17
+ def many_to_many_is_relationship?
18
+ self.type == 'many_to_many'
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ module Target
26
+
27
+ extend ActiveSupport::Concern
28
+
29
+ module ClassMethods
30
+
31
+ # Adds a many_to_many relationship between 2 mongoid models
32
+ #
33
+ # @param [ Class ] klass The class to modify
34
+ # @param [ Hash ] rule It contains the name of the relation and if it is required or not
35
+ #
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
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
40
+
41
+ # def at_least_one_element?
42
+ # (base.send(metadata.key.to_sym).try(:size) || 0) > 0
43
+ # end
44
+
45
+ def filtered(conditions = {}, order_by = nil)
46
+ list = conditions.empty? ? self : self.where(conditions)
47
+
48
+ if order_by
49
+ list.order_by(order_by)
50
+ else
51
+ # use the natural order given by the initial array (ex: project_ids).
52
+ # Warning: it returns an array and not a criteria object meaning it breaks the chain
53
+ ids = base.send(metadata.key.to_sym)
54
+ list.entries.sort { |a, b| ids.index(a.id) <=> ids.index(b.id) }
55
+ end
56
+ end
57
+
58
+ alias :ordered :filtered # backward compatibility + semantic purpose
59
+
60
+ end
61
+
62
+ if rule['required']
63
+ klass.validates_collection_size_of rule['name'], minimum: 1, message: :at_least_one_element
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -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
@@ -0,0 +1,44 @@
1
+ module CustomFields
2
+
3
+ module Types
4
+
5
+ module RelationshipDefault
6
+
7
+ module Field
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+
13
+ field :class_name
14
+ field :inverse_of
15
+ field :order_by
16
+
17
+ validates_presence_of :class_name, if: :is_relationship?
18
+ validate :ensure_class_name_security, if: :is_relationship?
19
+
20
+ def is_relationship?
21
+ method_name = :"#{self.type}_is_relationship?"
22
+ self.respond_to?(method_name) && self.send(method_name)
23
+ end
24
+
25
+ protected
26
+
27
+ def ensure_class_name_security
28
+ true
29
+ # FIXME: to be overridden in the target application
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+
43
+
44
+
@@ -0,0 +1,217 @@
1
+ module CustomFields
2
+
3
+ module Types
4
+
5
+ module Select
6
+
7
+ class Option
8
+
9
+ include Mongoid::Document
10
+
11
+ field :name, localize: true
12
+ field :position, type: ::Integer, default: 0
13
+
14
+ embedded_in :custom_field, inverse_of: :select_options
15
+
16
+ validates_presence_of :name
17
+
18
+ def as_json(options = nil)
19
+ super methods: %w(_id name position)
20
+ end
21
+
22
+ end
23
+
24
+ module Field
25
+
26
+ extend ActiveSupport::Concern
27
+
28
+ included do
29
+
30
+ embeds_many :select_options, class_name: 'CustomFields::Types::Select::Option'
31
+
32
+ validates_associated :select_options
33
+
34
+ accepts_nested_attributes_for :select_options, allow_destroy: true
35
+
36
+ end
37
+
38
+ def ordered_select_options
39
+ self.select_options.sort { |a, b| (a.position || 0) <=> (b.position || 0) }.to_a
40
+ end
41
+
42
+ def select_to_recipe
43
+ {
44
+ 'select_options' => self.ordered_select_options.map do |option|
45
+ { '_id' => option._id, 'name' => option.name_translations }
46
+ end
47
+ }
48
+ end
49
+
50
+ def select_as_json(options = {})
51
+ { 'select_options' => self.ordered_select_options.map(&:as_json) }
52
+ end
53
+
54
+ end
55
+
56
+ module Target
57
+
58
+ extend ActiveSupport::Concern
59
+
60
+ module ClassMethods
61
+
62
+ # Adds a select field
63
+ #
64
+ # @param [ Class ] klass The class to modify
65
+ # @param [ Hash ] rule It contains the name of the field and if it is required or not
66
+ #
67
+ def apply_select_custom_field(klass, rule)
68
+ name, base_collection_name = rule['name'], "#{rule['name']}_options".to_sym
69
+
70
+ klass.field :"#{name}_id", type: Moped::BSON::ObjectId, localize: rule['localized'] || false
71
+
72
+ klass.cattr_accessor "_raw_#{base_collection_name}"
73
+ klass.send :"_raw_#{base_collection_name}=", rule['select_options'].sort { |a, b| a['position'] <=> b['position'] }
74
+
75
+ # other methods
76
+ klass.send(:define_method, name.to_sym) { _get_select_option(name) }
77
+ klass.send(:define_method, :"#{name}=") { |value| _set_select_option(name, value) }
78
+
79
+ klass.class_eval <<-EOV
80
+
81
+ def self.#{base_collection_name}
82
+ self._select_options('#{name}')
83
+ end
84
+
85
+ EOV
86
+
87
+ if rule['required']
88
+ klass.validates_presence_of name
89
+ end
90
+ end
91
+
92
+ # Build a hash storing the values (id and option name) for
93
+ # a select custom field of an instance.
94
+ #
95
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
96
+ # @param [ String ] name The name of the select custom field
97
+ #
98
+ # @return [ Hash ] fields: <name>: option name, <name>_id: id of the option
99
+ #
100
+ def select_attribute_get(instance, name)
101
+ if value = instance.send(name.to_sym)
102
+ {
103
+ name => value,
104
+ "#{name}_id" => instance.send(:"#{name}_id")
105
+ }
106
+ else
107
+ {}
108
+ end
109
+ end
110
+
111
+ # Set the value for the instance and the select field specified by
112
+ # the 2 params.
113
+ #
114
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
115
+ # @param [ String ] name The name of the select custom field
116
+ # @param [ Hash ] attributes The attributes used to fetch the values
117
+ #
118
+ def select_attribute_set(instance, name, attributes)
119
+ id_or_name = attributes[name] || attributes["#{name}_id"]
120
+
121
+ return if id_or_name.nil?
122
+
123
+ option = _select_options(name).detect do |option|
124
+ [option['_id'], option['name']].include?(id_or_name)
125
+ end
126
+
127
+ instance.send(:"#{name}_id=", option.try(:[], '_id'))
128
+ end
129
+
130
+ # Returns a list of documents groupes by select values defined in the custom fields recipe
131
+ #
132
+ # @param [ Class ] klass The class to modify
133
+ # @return [ Array ] An array of hashes (keys: select option and related documents)
134
+ #
135
+ def group_by_select_option(name, order_by = nil)
136
+ name_id = "#{name}_id"
137
+ groups = self.each.group_by{|x| x.send(name_id)}.map do |(k, v)|
138
+ {name_id => k, "group" => v}
139
+ end
140
+
141
+ _select_options(name).map do |option|
142
+ group = groups.detect { |g| g[name_id].to_s == option['_id'].to_s }
143
+ list = group ? group['group'] : []
144
+
145
+ groups.delete(group) if group
146
+
147
+ { name: option['name'], entries: self._order_select_entries(list, order_by) }.with_indifferent_access
148
+ end.tap do |array|
149
+ if not groups.empty? # orphan entries ?
150
+ empty = { name: nil, entries: [] }.with_indifferent_access
151
+ groups.each do |group|
152
+ empty[:entries] += group['group']
153
+ end
154
+ empty[:entries] = self._order_select_entries(empty[:entries], order_by)
155
+ array << empty
156
+ end
157
+ end
158
+ end
159
+
160
+ def _select_options(name)
161
+ self.send(:"_raw_#{name}_options").map do |option|
162
+
163
+ locale = Mongoid::Fields::I18n.locale.to_s
164
+
165
+ name = if !option['name'].respond_to?(:merge)
166
+ option['name']
167
+ elsif Mongoid::Fields::I18n.fallbacks?
168
+ option['name'][Mongoid::Fields::I18n.fallbacks[locale.to_sym].map(&:to_s).find { |loc| !option['name'][loc].nil? }]
169
+ else
170
+ option['name'][locale.to_s]
171
+ end
172
+
173
+ { '_id' => option['_id'], 'name' => name }
174
+ end
175
+ end
176
+
177
+ def _order_select_entries(list, order_by = nil)
178
+ return list if order_by.nil?
179
+
180
+ column, direction = order_by.flatten
181
+
182
+ list = list.sort { |a, b| (a.send(column) && b.send(column)) ? (a.send(column) || 0) <=> (b.send(column) || 0) : 0 }
183
+
184
+ direction == 'asc' ? list : list.reverse
185
+
186
+ list
187
+ end
188
+
189
+ end
190
+
191
+ def _select_option_id(name)
192
+ self.send(:"#{name}_id")
193
+ end
194
+
195
+ def _find_select_option(name, id_or_name)
196
+ self.class._select_options(name).detect do |option|
197
+ option['name'] == id_or_name || option['_id'].to_s == id_or_name.to_s
198
+ end
199
+ end
200
+
201
+ def _get_select_option(name)
202
+ option = self._find_select_option(name, self._select_option_id(name))
203
+ option ? option['name'] : nil
204
+ end
205
+
206
+ def _set_select_option(name, value)
207
+ option = self._find_select_option(name, value)
208
+ self.send(:"#{name}_id=", option ? option['_id'] : nil)
209
+ end
210
+
211
+ end
212
+
213
+ end
214
+
215
+ end
216
+
217
+ end
@@ -0,0 +1,55 @@
1
+ module CustomFields
2
+
3
+ module Types
4
+
5
+ module String
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_string_custom_field(klass, rule)
21
+ apply_custom_field(klass, rule)
22
+ end
23
+
24
+ # Build a hash storing the raw value for
25
+ # a string custom field of an instance.
26
+ #
27
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
28
+ # @param [ String ] name The name of the string custom field
29
+ #
30
+ # @return [ Hash ] field name => raw value
31
+ #
32
+ def string_attribute_get(instance, name)
33
+ self.default_attribute_get(instance, name)
34
+ end
35
+
36
+ # Set the value for the instance and the string field specified by
37
+ # the 2 params.
38
+ #
39
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
40
+ # @param [ String ] name The name of the string custom field
41
+ # @param [ Hash ] attributes The attributes used to fetch the values
42
+ #
43
+ def string_attribute_set(instance, name, attributes)
44
+ self.default_attribute_set(instance, name, attributes)
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,35 @@
1
+ module CustomFields
2
+ module Types
3
+ module Tags
4
+ module Field; end
5
+
6
+ module Target
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ def apply_tags_custom_field(klass, rule)
12
+ klass.field rule['name'], localize: rule['localized'] || false, type: Array
13
+
14
+ klass.class_eval do
15
+ define_method("#{rule['name']}=") do |val|
16
+ #FIXME I would use is_a?(), but it doesn't work in my machine!
17
+ val = val.split(/ *, */) if val.class.to_s == "String"
18
+ super(val)
19
+ end
20
+ end
21
+ end
22
+
23
+ def tags_attribute_get(instance, name)
24
+ self.default_attribute_get(instance, name)
25
+ end
26
+
27
+ def tags_attribute_set(instance, name, attributes)
28
+ self.default_attribute_set(instance, name, attributes)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,65 @@
1
+ module CustomFields
2
+
3
+ module Types
4
+
5
+ module Text
6
+
7
+ module Field
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+
13
+ field :text_formatting, default: 'html'
14
+
15
+ end
16
+
17
+ end
18
+
19
+ module Target
20
+
21
+ extend ActiveSupport::Concern
22
+
23
+ module ClassMethods
24
+
25
+ # Adds a text field (simply a string field)
26
+ #
27
+ # @param [ Class ] klass The class to modify
28
+ # @param [ Hash ] rule It contains the name of the field and if it is required or not
29
+ #
30
+ def apply_text_custom_field(klass, rule)
31
+ apply_custom_field(klass, rule)
32
+ end
33
+
34
+ # Build a hash storing the raw value for
35
+ # a string custom field of an instance.
36
+ #
37
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
38
+ # @param [ String ] name The name of the string custom field
39
+ #
40
+ # @return [ Hash ] field name => raw value
41
+ #
42
+ def text_attribute_get(instance, name)
43
+ default_attribute_get(instance, name)
44
+ end
45
+
46
+ # Set the value for the instance and the text field specified by
47
+ # the 2 params.
48
+ #
49
+ # @param [ Object ] instance An instance of the class enhanced by the custom_fields
50
+ # @param [ String ] name The name of the text custom field
51
+ # @param [ Hash ] attributes The attributes used to fetch the values
52
+ #
53
+ def text_attribute_set(instance, name, attributes)
54
+ self.default_attribute_set(instance, name, attributes)
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ module CustomFields #:nodoc
3
+
4
+ VERSION = '2.3.1'
5
+
6
+ end