custom_fields 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +1 -1
- data/README.textile +4 -4
- data/config/locales/de.yml +11 -5
- data/config/locales/en.yml +4 -0
- data/config/locales/fr.yml +4 -0
- data/config/locales/ru.yml +15 -0
- data/lib/custom_fields.rb +37 -27
- data/lib/custom_fields/extensions/active_support.rb +1 -1
- data/lib/custom_fields/extensions/carrierwave.rb +2 -2
- data/lib/custom_fields/extensions/mongoid/factory.rb +3 -3
- data/lib/custom_fields/extensions/mongoid/fields.rb +4 -3
- data/lib/custom_fields/extensions/mongoid/fields/localized.rb +39 -0
- data/lib/custom_fields/extensions/mongoid/relations/referenced/in.rb +1 -1
- data/lib/custom_fields/extensions/mongoid/relations/referenced/many.rb +3 -3
- data/lib/custom_fields/field.rb +14 -8
- data/lib/custom_fields/source.rb +23 -14
- data/lib/custom_fields/target.rb +16 -8
- data/lib/custom_fields/target_helpers.rb +6 -6
- data/lib/custom_fields/types/belongs_to.rb +4 -4
- data/lib/custom_fields/types/boolean.rb +1 -1
- data/lib/custom_fields/types/date.rb +2 -2
- data/lib/custom_fields/types/default.rb +5 -6
- data/lib/custom_fields/types/email.rb +60 -0
- data/lib/custom_fields/types/file.rb +2 -2
- data/lib/custom_fields/types/float.rb +52 -0
- data/lib/custom_fields/types/has_many.rb +6 -8
- data/lib/custom_fields/types/integer.rb +54 -0
- data/lib/custom_fields/types/many_to_many.rb +3 -3
- data/lib/custom_fields/types/money.rb +146 -0
- data/lib/custom_fields/types/relationship_default.rb +2 -2
- data/lib/custom_fields/types/select.rb +16 -13
- data/lib/custom_fields/types/tags.rb +35 -0
- data/lib/custom_fields/types/text.rb +1 -1
- data/lib/custom_fields/version.rb +1 -1
- metadata +65 -76
- data/init.rb +0 -2
- data/lib/custom_fields/extensions/mongoid/fields/internal/localized.rb +0 -86
data/MIT-LICENSE
CHANGED
data/README.textile
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
h1. CustomFields
|
4
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
|
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
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
7
|
|
8
8
|
The main goals:
|
@@ -12,7 +12,7 @@ The main goals:
|
|
12
12
|
|
13
13
|
h2. Requirements
|
14
14
|
|
15
|
-
ActiveSupport 3.2.
|
15
|
+
ActiveSupport 3.2.13, MongoDB 2.0 and Mongoid 3.1.3
|
16
16
|
|
17
17
|
h2. Examples
|
18
18
|
|
@@ -42,7 +42,7 @@ company.save
|
|
42
42
|
company.employees.build :name => 'Michael Scott', :position => 'Regional manager'
|
43
43
|
|
44
44
|
another_company = Company.new
|
45
|
-
employee =
|
45
|
+
employee = another_company.employees.build
|
46
46
|
employee.position # returns a "not defined method" error
|
47
47
|
|
48
48
|
h3. On the class itself
|
@@ -67,4 +67,4 @@ h2. Contact
|
|
67
67
|
|
68
68
|
Feel free to contact me at didier at nocoffee dot fr.
|
69
69
|
|
70
|
-
Copyright (c)
|
70
|
+
Copyright (c) 2013 NoCoffee, released under the MIT license
|
data/config/locales/de.yml
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
de:
|
2
2
|
custom_fields:
|
3
3
|
type:
|
4
|
-
string:
|
5
|
-
text: Text
|
6
|
-
select:
|
7
|
-
boolean:
|
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
|
8
11
|
date: Datum
|
9
|
-
file: Datei
|
12
|
+
file: Datei
|
13
|
+
belongs_to: gehört zu
|
14
|
+
has_many: Hat viele
|
15
|
+
many_to_many: Hat und gehört zu vielen
|
data/config/locales/en.yml
CHANGED
data/config/locales/fr.yml
CHANGED
@@ -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: Многие ко многим
|
data/lib/custom_fields.rb
CHANGED
@@ -2,11 +2,13 @@ $:.unshift File.expand_path(File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
require 'active_support'
|
4
4
|
require 'carrierwave/mongoid'
|
5
|
+
require 'money'
|
5
6
|
|
6
7
|
module CustomFields
|
7
8
|
|
8
9
|
@@options = {
|
9
|
-
:
|
10
|
+
reserved_names: Mongoid.destructive_fields + %w(id _id send class),
|
11
|
+
default_currency: 'EUR'
|
10
12
|
}
|
11
13
|
|
12
14
|
def self.options=(options)
|
@@ -19,31 +21,39 @@ module CustomFields
|
|
19
21
|
|
20
22
|
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
24
|
+
%w( version
|
25
|
+
extensions/active_support
|
26
|
+
extensions/carrierwave
|
27
|
+
extensions/mongoid/document
|
28
|
+
extensions/mongoid/factory
|
29
|
+
extensions/mongoid/relations/referenced/many
|
30
|
+
extensions/mongoid/relations/referenced/in
|
31
|
+
extensions/mongoid/fields.rb
|
32
|
+
extensions/mongoid/fields/i18n.rb
|
33
|
+
extensions/mongoid/fields/localized.rb
|
34
|
+
types/default
|
35
|
+
types/string
|
36
|
+
types/text
|
37
|
+
types/email
|
38
|
+
types/date
|
39
|
+
types/boolean
|
40
|
+
types/file
|
41
|
+
types/select
|
42
|
+
types/integer
|
43
|
+
types/float
|
44
|
+
types/money
|
45
|
+
types/relationship_default
|
46
|
+
types/belongs_to
|
47
|
+
types/has_many
|
48
|
+
types/many_to_many
|
49
|
+
types/tags
|
50
|
+
field
|
51
|
+
source
|
52
|
+
target_helpers
|
53
|
+
target
|
54
|
+
).each do |lib|
|
55
|
+
require_relative "./custom_fields/#{lib}"
|
56
|
+
end
|
47
57
|
|
48
58
|
# Load all the translation files
|
49
59
|
I18n.load_path += Dir[File.join(File.dirname(__FILE__), '..', 'config', 'locales', '*.yml')]
|
@@ -57,4 +67,4 @@ module MyBenchmark
|
|
57
67
|
returned_value
|
58
68
|
end
|
59
69
|
|
60
|
-
end
|
70
|
+
end
|
@@ -12,7 +12,7 @@ module CarrierWave
|
|
12
12
|
|
13
13
|
value = read_attribute(name.to_sym)
|
14
14
|
unless value.nil?
|
15
|
-
self.class.fields[name.to_s].
|
15
|
+
self.class.fields[name.to_s].demongoize(value)
|
16
16
|
else
|
17
17
|
nil
|
18
18
|
end
|
@@ -22,4 +22,4 @@ module CarrierWave
|
|
22
22
|
alias_method_chain :mount_uploader, :localization
|
23
23
|
end
|
24
24
|
|
25
|
-
end
|
25
|
+
end
|
@@ -4,11 +4,11 @@ module Mongoid #:nodoc:
|
|
4
4
|
# Instantiates documents that came from the database.
|
5
5
|
module Factory
|
6
6
|
|
7
|
-
def from_db_with_custom_fields(klass, attributes = {})
|
7
|
+
def from_db_with_custom_fields(klass, attributes = {}, criteria_instance_id = nil)
|
8
8
|
if klass.with_custom_fields?
|
9
9
|
klass.klass_with_custom_fields(attributes['custom_fields_recipe'])
|
10
10
|
end
|
11
|
-
from_db_without_custom_fields(klass, attributes)
|
11
|
+
from_db_without_custom_fields(klass, attributes, criteria_instance_id)
|
12
12
|
end
|
13
13
|
|
14
14
|
# equivalent for "alias_method_chain :from_db, :custom_fields"
|
@@ -17,4 +17,4 @@ module Mongoid #:nodoc:
|
|
17
17
|
|
18
18
|
end
|
19
19
|
|
20
|
-
end
|
20
|
+
end
|
@@ -19,12 +19,13 @@ module Mongoid #:nodoc
|
|
19
19
|
# @since 2.1.0
|
20
20
|
def replace_field(name, type, localize = false)
|
21
21
|
# puts "fields[#{name}] = #{fields[name.to_s].inspect} / #{fields.keys.inspect}" # DEBUG
|
22
|
-
|
23
|
-
|
22
|
+
#attribute_names.delete_one(name)
|
23
|
+
remove_defaults(name)
|
24
|
+
add_field(name, fields[name.to_s].options.merge(type: type, localize: localize))
|
24
25
|
end
|
25
26
|
|
26
27
|
end
|
27
28
|
|
28
29
|
end
|
29
30
|
|
30
|
-
end
|
31
|
+
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
|
@@ -14,13 +14,13 @@ module Mongoid #:nodoc:
|
|
14
14
|
|
15
15
|
attributes ||= {}
|
16
16
|
|
17
|
-
attributes.merge!(:
|
17
|
+
attributes.merge!(custom_fields_recipe: recipe)
|
18
18
|
|
19
19
|
# build the class with custom_fields for the first time
|
20
20
|
type = metadata.klass.klass_with_custom_fields(recipe)
|
21
21
|
end
|
22
|
-
|
23
22
|
build_without_custom_fields(attributes, options, type)
|
23
|
+
|
24
24
|
end
|
25
25
|
|
26
26
|
alias_method_chain :build, :custom_fields
|
@@ -31,4 +31,4 @@ module Mongoid #:nodoc:
|
|
31
31
|
|
32
32
|
end
|
33
33
|
end
|
34
|
-
end
|
34
|
+
end
|
data/lib/custom_fields/field.rb
CHANGED
@@ -6,7 +6,8 @@ module CustomFields
|
|
6
6
|
include ::Mongoid::Timestamps
|
7
7
|
|
8
8
|
## types ##
|
9
|
-
%w(default string text date boolean file select
|
9
|
+
%w(default string text email date boolean file select float integer money
|
10
|
+
relationship_default belongs_to has_many many_to_many).each do |type|
|
10
11
|
include "CustomFields::Types::#{type.classify}::Field".constantize
|
11
12
|
end
|
12
13
|
|
@@ -15,14 +16,15 @@ module CustomFields
|
|
15
16
|
field :name
|
16
17
|
field :type
|
17
18
|
field :hint
|
18
|
-
field :position, :
|
19
|
-
field :required, :
|
20
|
-
field :
|
19
|
+
field :position, type: ::Integer, default: 0
|
20
|
+
field :required, type: ::Boolean, default: false
|
21
|
+
field :unique, type: ::Boolean, default: false
|
22
|
+
field :localized, type: ::Boolean, default: false
|
21
23
|
|
22
24
|
## validations ##
|
23
25
|
validates_presence_of :label, :type
|
24
|
-
validates_exclusion_of :name, :
|
25
|
-
validates_format_of :name, :
|
26
|
+
validates_exclusion_of :name, in: lambda { |f| CustomFields.options[:reserved_names].map(&:to_s) }
|
27
|
+
validates_format_of :name, with: /^[a-z]([A-Za-z0-9_]+)?$/
|
26
28
|
validate :uniqueness_of_label_and_name
|
27
29
|
|
28
30
|
## callbacks ##
|
@@ -58,7 +60,11 @@ module CustomFields
|
|
58
60
|
method_name = :"#{self.type}_to_recipe"
|
59
61
|
custom_to_recipe = self.send(method_name) rescue {}
|
60
62
|
|
61
|
-
{ 'name'
|
63
|
+
{ 'name' => self.name,
|
64
|
+
'type' => self.type,
|
65
|
+
'required' => self.required?,
|
66
|
+
'unique' => self.unique?,
|
67
|
+
'localized' => self.localized? }.merge(custom_to_recipe)
|
62
68
|
end
|
63
69
|
|
64
70
|
def as_json(options = {})
|
@@ -94,4 +100,4 @@ module CustomFields
|
|
94
100
|
|
95
101
|
end
|
96
102
|
|
97
|
-
end
|
103
|
+
end
|
data/lib/custom_fields/source.rb
CHANGED
@@ -65,7 +65,8 @@ module CustomFields
|
|
65
65
|
{
|
66
66
|
'name' => "#{self.relations[name.to_s].class_name.demodulize}#{self._id}",
|
67
67
|
'rules' => self.ordered_custom_fields(name).map(&:to_recipe),
|
68
|
-
'version' => self.custom_fields_version(name)
|
68
|
+
'version' => self.custom_fields_version(name),
|
69
|
+
'model_name' => self.relations[name.to_s].class_name.constantize.model_name
|
69
70
|
}
|
70
71
|
end
|
71
72
|
|
@@ -111,10 +112,18 @@ module CustomFields
|
|
111
112
|
|
112
113
|
metadata.instance_variable_set(:@klass, self.klass_with_custom_fields(name))
|
113
114
|
end
|
114
|
-
|
115
|
+
set_attribute_localization(name)
|
115
116
|
# puts "new_metadata = #{self.send(name).metadata.klass.inspect} / #{self.send(name).metadata.object_id.inspect}" # DEBUG
|
116
117
|
end
|
117
118
|
|
119
|
+
def set_attribute_localization(name)
|
120
|
+
klass_name = name.singularize.to_sym
|
121
|
+
self.send(:"#{name}_custom_fields").each do |cf|
|
122
|
+
I18n.backend.store_translations ::I18n.locale,
|
123
|
+
{mongoid: { attributes: {klass_name => {cf.name.to_sym => cf.label}}}}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
118
127
|
# Initializes the object tracking the modifications
|
119
128
|
# of the custom fields
|
120
129
|
#
|
@@ -145,7 +154,7 @@ module CustomFields
|
|
145
154
|
# collect fields with a modified localized field
|
146
155
|
fields.each do |field|
|
147
156
|
if field.localized_changed? && field.persisted?
|
148
|
-
self._custom_field_localize_diff[name] << { :
|
157
|
+
self._custom_field_localize_diff[name] << { field: field.name, localized: field.localized? }
|
149
158
|
end
|
150
159
|
end
|
151
160
|
end
|
@@ -165,14 +174,14 @@ module CustomFields
|
|
165
174
|
|
166
175
|
# puts "selector = #{selector.inspect}, memo = #{attributes.inspect}" # DEBUG
|
167
176
|
|
168
|
-
collection.update
|
177
|
+
collection.find(selector).update operations, multi: true
|
169
178
|
end
|
170
179
|
|
171
180
|
# If the localized attribute has been changed in at least one of the custom fields,
|
172
181
|
# we have to upgrade all the records enhanced by custom_fields in order to make
|
173
182
|
# the values consistent with the mongoid localize option.
|
174
183
|
#
|
175
|
-
# Ex: post.attributes[:name] = 'Hello world' => post.attributes[:name] = { :
|
184
|
+
# Ex: post.attributes[:name] = 'Hello world' => post.attributes[:name] = { en: 'Hello world' }
|
176
185
|
#
|
177
186
|
# @param [ String, Symbol ] name The name of the relation.
|
178
187
|
#
|
@@ -198,7 +207,7 @@ module CustomFields
|
|
198
207
|
next if updates.empty?
|
199
208
|
|
200
209
|
collection = self.send(name).collection
|
201
|
-
collection.
|
210
|
+
collection.find(record.atomic_selector).update({ '$set' => updates })
|
202
211
|
end
|
203
212
|
end
|
204
213
|
|
@@ -228,13 +237,13 @@ module CustomFields
|
|
228
237
|
# end
|
229
238
|
#
|
230
239
|
# class Employee
|
231
|
-
# embedded_in :company, :
|
240
|
+
# embedded_in :company, inverse_of: :employees
|
232
241
|
# field :name, String
|
233
242
|
# end
|
234
243
|
#
|
235
|
-
# company.employees_custom_fields.build :
|
244
|
+
# company.employees_custom_fields.build label: 'His/her position', name: 'position', kind: 'string'
|
236
245
|
# company.save
|
237
|
-
# company.employees.build :
|
246
|
+
# company.employees.build name: 'Michael Scott', position: 'Regional manager'
|
238
247
|
#
|
239
248
|
def custom_fields_for(name)
|
240
249
|
self.declare_embedded_in_definition_in_custom_field(name)
|
@@ -254,11 +263,11 @@ module CustomFields
|
|
254
263
|
#
|
255
264
|
def extend_for_custom_fields(name)
|
256
265
|
class_eval do
|
257
|
-
field :"#{name}_custom_fields_version", :
|
266
|
+
field :"#{name}_custom_fields_version", type: ::Integer, default: 0
|
258
267
|
|
259
|
-
embeds_many :"#{name}_custom_fields", :
|
268
|
+
embeds_many :"#{name}_custom_fields", class_name: self.dynamic_custom_field_class_name(name) #, cascade_callbacks: true # FIXME ?????
|
260
269
|
|
261
|
-
accepts_nested_attributes_for :"#{name}_custom_fields", :
|
270
|
+
accepts_nested_attributes_for :"#{name}_custom_fields", allow_destroy: true
|
262
271
|
end
|
263
272
|
|
264
273
|
class_eval <<-EOV
|
@@ -324,7 +333,7 @@ module CustomFields
|
|
324
333
|
|
325
334
|
unless source.const_defined?(klass_name)
|
326
335
|
(klass = Class.new(::CustomFields::Field)).class_eval <<-EOF
|
327
|
-
embedded_in :#{self.name.demodulize.underscore}, :
|
336
|
+
embedded_in :#{self.name.demodulize.underscore}, inverse_of: :#{name}_custom_fields, class_name: '#{self.name}'
|
328
337
|
EOF
|
329
338
|
|
330
339
|
source.const_set(klass_name, klass)
|
@@ -335,4 +344,4 @@ module CustomFields
|
|
335
344
|
|
336
345
|
end
|
337
346
|
|
338
|
-
end
|
347
|
+
end
|