gnuside-custom_fields 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|