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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +70 -0
- data/config/locales/de.yml +15 -0
- data/config/locales/en.yml +21 -0
- data/config/locales/fr.yml +25 -0
- data/config/locales/pt-BR.yml +9 -0
- data/config/locales/ru.yml +15 -0
- data/lib/custom_fields/extensions/active_support.rb +28 -0
- data/lib/custom_fields/extensions/carrierwave.rb +25 -0
- data/lib/custom_fields/extensions/mongoid/document.rb +21 -0
- data/lib/custom_fields/extensions/mongoid/factory.rb +20 -0
- data/lib/custom_fields/extensions/mongoid/fields/i18n.rb +55 -0
- data/lib/custom_fields/extensions/mongoid/fields/localized.rb +39 -0
- data/lib/custom_fields/extensions/mongoid/fields.rb +31 -0
- data/lib/custom_fields/extensions/mongoid/relations/referenced/in.rb +22 -0
- data/lib/custom_fields/extensions/mongoid/relations/referenced/many.rb +34 -0
- data/lib/custom_fields/extensions/mongoid/validations/collection_size.rb +43 -0
- data/lib/custom_fields/extensions/mongoid/validations/macros.rb +25 -0
- data/lib/custom_fields/extensions/origin/smash.rb +33 -0
- data/lib/custom_fields/field.rb +106 -0
- data/lib/custom_fields/source.rb +347 -0
- data/lib/custom_fields/target.rb +99 -0
- data/lib/custom_fields/target_helpers.rb +192 -0
- data/lib/custom_fields/types/belongs_to.rb +65 -0
- data/lib/custom_fields/types/boolean.rb +55 -0
- data/lib/custom_fields/types/date.rb +97 -0
- data/lib/custom_fields/types/date_time.rb +97 -0
- data/lib/custom_fields/types/default.rb +103 -0
- data/lib/custom_fields/types/email.rb +60 -0
- data/lib/custom_fields/types/file.rb +74 -0
- data/lib/custom_fields/types/float.rb +52 -0
- data/lib/custom_fields/types/has_many.rb +74 -0
- data/lib/custom_fields/types/integer.rb +54 -0
- data/lib/custom_fields/types/many_to_many.rb +75 -0
- data/lib/custom_fields/types/money.rb +146 -0
- data/lib/custom_fields/types/relationship_default.rb +44 -0
- data/lib/custom_fields/types/select.rb +217 -0
- data/lib/custom_fields/types/string.rb +55 -0
- data/lib/custom_fields/types/tags.rb +35 -0
- data/lib/custom_fields/types/text.rb +65 -0
- data/lib/custom_fields/version.rb +6 -0
- data/lib/custom_fields.rb +74 -0
- 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,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
|