populate-me 0.12.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +655 -0
- data/Rakefile +14 -0
- data/example/config.ru +100 -0
- data/lib/populate_me.rb +2 -0
- data/lib/populate_me/admin.rb +157 -0
- data/lib/populate_me/admin/__assets__/css/asmselect.css +63 -0
- data/lib/populate_me/admin/__assets__/css/jquery-ui.min.css +6 -0
- data/lib/populate_me/admin/__assets__/css/main.css +244 -0
- data/lib/populate_me/admin/__assets__/img/help/children.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/create.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/delete.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/edit.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/form.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/list.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/login.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/logout.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/menu.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/overview.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/save.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/sort.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/sublist.png +0 -0
- data/lib/populate_me/admin/__assets__/js/asmselect.js +412 -0
- data/lib/populate_me/admin/__assets__/js/columnav.js +87 -0
- data/lib/populate_me/admin/__assets__/js/jquery-ui.min.js +7 -0
- data/lib/populate_me/admin/__assets__/js/main.js +388 -0
- data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
- data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
- data/lib/populate_me/admin/views/help.erb +94 -0
- data/lib/populate_me/admin/views/page.erb +189 -0
- data/lib/populate_me/api.rb +124 -0
- data/lib/populate_me/attachment.rb +186 -0
- data/lib/populate_me/document.rb +192 -0
- data/lib/populate_me/document_mixins/admin_adapter.rb +149 -0
- data/lib/populate_me/document_mixins/callbacks.rb +125 -0
- data/lib/populate_me/document_mixins/outcasting.rb +83 -0
- data/lib/populate_me/document_mixins/persistence.rb +95 -0
- data/lib/populate_me/document_mixins/schema.rb +198 -0
- data/lib/populate_me/document_mixins/typecasting.rb +70 -0
- data/lib/populate_me/document_mixins/validation.rb +44 -0
- data/lib/populate_me/file_system_attachment.rb +40 -0
- data/lib/populate_me/grid_fs_attachment.rb +103 -0
- data/lib/populate_me/mongo.rb +160 -0
- data/lib/populate_me/s3_attachment.rb +120 -0
- data/lib/populate_me/variation.rb +38 -0
- data/lib/populate_me/version.rb +4 -0
- data/populate-me.gemspec +34 -0
- data/test/helper.rb +37 -0
- data/test/test_admin.rb +183 -0
- data/test/test_api.rb +246 -0
- data/test/test_attachment.rb +167 -0
- data/test/test_document.rb +128 -0
- data/test/test_document_admin_adapter.rb +221 -0
- data/test/test_document_callbacks.rb +151 -0
- data/test/test_document_outcasting.rb +247 -0
- data/test/test_document_persistence.rb +83 -0
- data/test/test_document_schema.rb +280 -0
- data/test/test_document_typecasting.rb +128 -0
- data/test/test_grid_fs_attachment.rb +239 -0
- data/test/test_mongo.rb +324 -0
- data/test/test_s3_attachment.rb +281 -0
- data/test/test_variation.rb +91 -0
- data/test/test_version.rb +11 -0
- metadata +294 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module DocumentMixins
|
3
|
+
module Outcasting
|
4
|
+
|
5
|
+
# This module prepares the field for being send to the Admin API
|
6
|
+
# and build the form.
|
7
|
+
# It compiles the value and all other info in a hash.
|
8
|
+
# Therefore, it is a complement to the AdminAdapter module.
|
9
|
+
|
10
|
+
def outcast field, item, o={}
|
11
|
+
item = item.dup
|
12
|
+
item[:input_name] = "#{o[:input_name_prefix]}[#{item[:field_name]}]"
|
13
|
+
unless item[:type]==:list
|
14
|
+
WebUtils.ensure_key! item, :input_value, self.__send__(field)
|
15
|
+
end
|
16
|
+
meth = "outcast_#{item[:type]}".to_sym
|
17
|
+
if respond_to?(meth)
|
18
|
+
__send__(meth, field, item, o)
|
19
|
+
else
|
20
|
+
item
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def outcast_list field, item, o={}
|
25
|
+
item = item.dup
|
26
|
+
item[:items] = self.__send__(field).map do |nested|
|
27
|
+
nested.to_admin_form(o.merge(input_name_prefix: item[:input_name]+'[]'))
|
28
|
+
end
|
29
|
+
item
|
30
|
+
end
|
31
|
+
|
32
|
+
def outcast_select field, item, o={}
|
33
|
+
item = item.dup
|
34
|
+
unless item[:select_options].nil?
|
35
|
+
if item[:multiple]==true
|
36
|
+
item[:input_name] = item[:input_name]+'[]'
|
37
|
+
end
|
38
|
+
opts = WebUtils.deep_copy(WebUtils.get_value(item[:select_options],self))
|
39
|
+
opts.map! do |opt|
|
40
|
+
if opt.is_a?(String)||opt.is_a?(Symbol)
|
41
|
+
opt = [opt.to_s.capitalize,opt]
|
42
|
+
end
|
43
|
+
if opt.is_a?(Array)
|
44
|
+
opt = {description: opt[0].to_s, value: opt[1].to_s}
|
45
|
+
end
|
46
|
+
if item[:input_value].respond_to?(:include?)
|
47
|
+
opt[:selected] = true if item[:input_value].include?(opt[:value])
|
48
|
+
else
|
49
|
+
opt[:selected] = true if item[:input_value]==opt[:value]
|
50
|
+
end
|
51
|
+
opt
|
52
|
+
end
|
53
|
+
if item[:multiple]
|
54
|
+
(item[:input_value]||[]).reverse.each do |iv|
|
55
|
+
opt = opts.find{|opt| opt[:value]==iv }
|
56
|
+
opts.unshift(opts.delete(opt)) unless opt.nil?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
item[:select_options] = opts
|
60
|
+
item
|
61
|
+
else
|
62
|
+
item
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def outcast_attachment field, item, o={}
|
67
|
+
item = item.dup
|
68
|
+
item[:url] = self.attachment(field).url
|
69
|
+
item
|
70
|
+
end
|
71
|
+
|
72
|
+
def outcast_price field, item, o={}
|
73
|
+
item = item.dup
|
74
|
+
if item[:input_value].is_a?(Integer)
|
75
|
+
item[:input_value] = WebUtils.display_price item[:input_value]
|
76
|
+
end
|
77
|
+
item
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module DocumentMixins
|
3
|
+
module Persistence
|
4
|
+
|
5
|
+
def persistent_instance_variables
|
6
|
+
instance_variables.select do |k|
|
7
|
+
if self.class.fields.empty?
|
8
|
+
k !~ /^@_/
|
9
|
+
else
|
10
|
+
self.class.fields.key? k[1..-1].to_sym
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def attachment f
|
16
|
+
attacher = WebUtils.resolve_class_name self.class.fields[f][:class_name]
|
17
|
+
attacher.new self, f
|
18
|
+
end
|
19
|
+
|
20
|
+
# Saving
|
21
|
+
def save
|
22
|
+
return unless valid?
|
23
|
+
exec_callback :before_save
|
24
|
+
if new?
|
25
|
+
exec_callback :before_create
|
26
|
+
id = perform_create
|
27
|
+
exec_callback :after_create
|
28
|
+
else
|
29
|
+
exec_callback :before_update
|
30
|
+
id = perform_update
|
31
|
+
exec_callback :after_update
|
32
|
+
end
|
33
|
+
exec_callback :after_save
|
34
|
+
id
|
35
|
+
end
|
36
|
+
def perform_create
|
37
|
+
self.class.documents << self.to_h
|
38
|
+
self.id
|
39
|
+
end
|
40
|
+
def perform_update
|
41
|
+
index = self.class.documents.index{|d| d['id']==self.id }
|
42
|
+
raise MissingDocumentError, "No document can be found with this ID: #{self.id}" if self.id.nil?||index.nil?
|
43
|
+
self.class.documents[index] = self.to_h
|
44
|
+
end
|
45
|
+
|
46
|
+
# Deletion
|
47
|
+
def delete o={}
|
48
|
+
exec_callback :before_delete
|
49
|
+
perform_delete
|
50
|
+
exec_callback :after_delete
|
51
|
+
end
|
52
|
+
def perform_delete
|
53
|
+
index = self.class.documents.index{|d| d['id']==self.id }
|
54
|
+
raise MissingDocumentError, "No document can be found with this ID: #{self.id}" if self.id.nil?||index.nil?
|
55
|
+
self.class.documents.delete_at(index)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.included(base)
|
59
|
+
base.extend(ClassMethods)
|
60
|
+
end
|
61
|
+
|
62
|
+
module ClassMethods
|
63
|
+
|
64
|
+
attr_writer :documents
|
65
|
+
|
66
|
+
def documents; @documents ||= []; end
|
67
|
+
|
68
|
+
def id_string_key
|
69
|
+
(self.fields.keys[0]||'id').to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_indexes f, ids=[]
|
73
|
+
if self.fields and self.fields[f.to_sym] and self.fields[f.to_sym][:direction]==:desc
|
74
|
+
ids = ids.dup.reverse
|
75
|
+
end
|
76
|
+
ids.each_with_index do |id,i|
|
77
|
+
self.documents.each do |d|
|
78
|
+
d[f.to_s] = i if d[self.id_string_key]==id
|
79
|
+
end
|
80
|
+
end
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def is_unique unique_id='unique'
|
85
|
+
if self.admin_get(unique_id).nil?
|
86
|
+
self.new.set_from_hash({id:unique_id}).save
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,198 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module DocumentMixins
|
3
|
+
module Schema
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
attr_writer :fields
|
12
|
+
attr_accessor :label_field
|
13
|
+
|
14
|
+
def fields; @fields ||= {}; end
|
15
|
+
|
16
|
+
def field name, o={}
|
17
|
+
set_id_field if self.fields.empty? and o[:type]!=:id
|
18
|
+
complete_field_options name, o
|
19
|
+
if o[:type]==:list
|
20
|
+
define_method(name) do
|
21
|
+
var = "@#{name}"
|
22
|
+
instance_variable_set(var, instance_variable_get(var)||[])
|
23
|
+
end
|
24
|
+
else
|
25
|
+
attr_accessor name
|
26
|
+
end
|
27
|
+
self.fields[name] = o
|
28
|
+
end
|
29
|
+
|
30
|
+
def complete_field_options name, o={}
|
31
|
+
o[:field_name] = name
|
32
|
+
WebUtils.ensure_key! o, :type, :string
|
33
|
+
WebUtils.ensure_key! o, :form_field, ![:id,:position].include?(o[:type])
|
34
|
+
o[:wrap] = false unless o[:form_field]
|
35
|
+
WebUtils.ensure_key! o, :wrap, ![:hidden,:list].include?(o[:type])
|
36
|
+
WebUtils.ensure_key! o, :label, WebUtils.label_for_field(name)
|
37
|
+
unless [:id, :polymorphic_type].include?(o[:type])
|
38
|
+
complete_only_for_field_option o
|
39
|
+
end
|
40
|
+
if o[:type]==:attachment
|
41
|
+
WebUtils.ensure_key! o, :class_name, settings.default_attachment_class
|
42
|
+
raise MissingAttachmentClassError, "No attachment class was provided for the #{self.name} field: #{name}" if o[:class_name].nil?
|
43
|
+
o[:class_name] = o[:class_name].name unless o[:class_name].is_a?(String)
|
44
|
+
end
|
45
|
+
if o[:type]==:list
|
46
|
+
o[:class_name] = WebUtils.guess_related_class_name(self.name,o[:class_name]||name)
|
47
|
+
o[:dasherized_class_name] = WebUtils.dasherize_class_name o[:class_name]
|
48
|
+
else
|
49
|
+
WebUtils.ensure_key! o, :input_attributes, {}
|
50
|
+
o[:input_attributes][:type] = :hidden if o[:type]==:hidden
|
51
|
+
unless o[:type]==:text
|
52
|
+
WebUtils.ensure_key! o[:input_attributes], :type, :text
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def complete_only_for_field_option o={}
|
58
|
+
if @currently_only_for
|
59
|
+
o[:only_for] = @currently_only_for
|
60
|
+
end
|
61
|
+
if o[:only_for].is_a?(String)
|
62
|
+
o[:only_for] = [o[:only_for]]
|
63
|
+
end
|
64
|
+
if o.key?(:only_for)
|
65
|
+
self.polymorphic unless self.polymorphic?
|
66
|
+
self.fields[:polymorphic_type][:values] += o[:only_for]
|
67
|
+
self.fields[:polymorphic_type][:values].uniq!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_id_field
|
72
|
+
field :id, {type: :id}
|
73
|
+
end
|
74
|
+
|
75
|
+
def position_field o={}
|
76
|
+
name = o[:name]||:position
|
77
|
+
o[:type] = :position
|
78
|
+
field name, o
|
79
|
+
sort_by name, (o[:direction]||:asc)
|
80
|
+
end
|
81
|
+
|
82
|
+
def polymorphic o={}
|
83
|
+
WebUtils.ensure_key! o, :type, :polymorphic_type
|
84
|
+
WebUtils.ensure_key! o, :values, []
|
85
|
+
WebUtils.ensure_key! o, :wrap, false
|
86
|
+
WebUtils.ensure_key! o, :input_attributes, {}
|
87
|
+
WebUtils.ensure_key! o[:input_attributes], :type, :hidden
|
88
|
+
field(:polymorphic_type, o) unless self.polymorphic?
|
89
|
+
end
|
90
|
+
|
91
|
+
def only_for polymorphic_type_values, &bloc
|
92
|
+
@currently_only_for = polymorphic_type_values
|
93
|
+
yield if block_given?
|
94
|
+
remove_instance_variable(:@currently_only_for)
|
95
|
+
end
|
96
|
+
|
97
|
+
def polymorphic?
|
98
|
+
self.fields.key? :polymorphic_type
|
99
|
+
end
|
100
|
+
|
101
|
+
def field_applicable? f, p_type=nil
|
102
|
+
applicable? :fields, f, p_type
|
103
|
+
end
|
104
|
+
|
105
|
+
def relationship_applicable? f, p_type=nil
|
106
|
+
applicable? :relationships, f, p_type
|
107
|
+
end
|
108
|
+
|
109
|
+
def applicable? target, f, p_type=nil
|
110
|
+
return false unless self.__send__(target).key?(f)
|
111
|
+
return true unless self.polymorphic?
|
112
|
+
return true unless self.__send__(target)[f].key?(:only_for)
|
113
|
+
return true if p_type.nil?
|
114
|
+
self.__send__(target)[f][:only_for].include? p_type
|
115
|
+
end
|
116
|
+
private :applicable?
|
117
|
+
|
118
|
+
def label sym # sets the label_field
|
119
|
+
@label_field = sym.to_sym
|
120
|
+
end
|
121
|
+
|
122
|
+
def label_field
|
123
|
+
return @label_field if self.fields.empty?
|
124
|
+
@label_field || self.fields.find do |k,v|
|
125
|
+
not [:id,:polymorphic_type].include?(v[:type])
|
126
|
+
end[0]
|
127
|
+
end
|
128
|
+
|
129
|
+
def sort_by f, direction=:asc
|
130
|
+
raise(ArgumentError) unless [:asc,:desc].include? direction
|
131
|
+
raise(ArgumentError) unless self.new.respond_to? f
|
132
|
+
@sort_proc = Proc.new do |a,b|
|
133
|
+
a,b = b,a if direction==:desc
|
134
|
+
a.__send__(f)<=>b.__send__(f)
|
135
|
+
end
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def relationships; @relationships ||= {}; end
|
140
|
+
|
141
|
+
def relationship name, o={}
|
142
|
+
o[:class_name] = WebUtils.guess_related_class_name(self.name,o[:class_name]||name)
|
143
|
+
WebUtils.ensure_key! o, :label, name.to_s.gsub('_',' ').capitalize
|
144
|
+
WebUtils.ensure_key! o, :foreign_key, "#{WebUtils.dasherize_class_name(self.name).gsub('-','_')}_id"
|
145
|
+
o[:foreign_key] = o[:foreign_key].to_sym
|
146
|
+
WebUtils.ensure_key! o, :dependent, true
|
147
|
+
complete_only_for_field_option o
|
148
|
+
self.relationships[name] = o
|
149
|
+
|
150
|
+
define_method(name) do
|
151
|
+
var = "@cached_#{name}"
|
152
|
+
instance_variable_set(var, instance_variable_get(var)||WebUtils.resolve_class_name(o[:class_name]).admin_find(query: {o[:foreign_key]=>self.id}))
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method("#{name}_first".to_sym) do
|
156
|
+
var = "@cached_#{name}_first"
|
157
|
+
instance_variable_set(var, instance_variable_get(var)||WebUtils.resolve_class_name(o[:class_name]).admin_find_first(query: {o[:foreign_key]=>self.id}))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_select_options o={}
|
162
|
+
proc do
|
163
|
+
items = self.admin_find({
|
164
|
+
query: (o[:query]||{}),
|
165
|
+
fields: [self.id_string_key, self.label_field, self.admin_image_field].compact.uniq
|
166
|
+
})
|
167
|
+
output = items.sort_by do |i|
|
168
|
+
i.to_s.downcase
|
169
|
+
end.map do |i|
|
170
|
+
item = { description: i.to_s, value: i.id }
|
171
|
+
unless self.admin_image_field.nil?
|
172
|
+
item.merge! preview_uri: i.admin_image_url
|
173
|
+
end
|
174
|
+
item
|
175
|
+
end
|
176
|
+
output.unshift(description: '?', value: '') if o[:allow_empty]
|
177
|
+
output
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
# Instance methods
|
184
|
+
|
185
|
+
def field_applicable? f
|
186
|
+
p_type = self.class.polymorphic? ? self.polymorphic_type : nil
|
187
|
+
self.class.field_applicable? f, p_type
|
188
|
+
end
|
189
|
+
|
190
|
+
def relationship_applicable? f
|
191
|
+
p_type = self.class.polymorphic? ? self.polymorphic_type : nil
|
192
|
+
self.class.relationship_applicable? f, p_type
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module DocumentMixins
|
3
|
+
module Typecasting
|
4
|
+
|
5
|
+
# This module deals with typecasting the fields
|
6
|
+
# when they are received as strings,
|
7
|
+
# generally from a form or a csv file
|
8
|
+
|
9
|
+
def typecast k, v
|
10
|
+
unless self.class.fields.key?(k)
|
11
|
+
return WebUtils.automatic_typecast(v)
|
12
|
+
end
|
13
|
+
f = self.class.fields[k].dup
|
14
|
+
meth = "typecast_#{f[:type]}".to_sym
|
15
|
+
unless respond_to? meth
|
16
|
+
return WebUtils.automatic_typecast(v, [f[:type],:nil])
|
17
|
+
end
|
18
|
+
__send__ meth, k, v
|
19
|
+
end
|
20
|
+
|
21
|
+
def typecast_integer k, v
|
22
|
+
v.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def typecast_price k, v
|
26
|
+
return nil if WebUtils.blank?(v)
|
27
|
+
WebUtils.parse_price(v)
|
28
|
+
end
|
29
|
+
|
30
|
+
def typecast_select k, v
|
31
|
+
if v.is_a?(Array)
|
32
|
+
v.reject{|str| str=='nil' }
|
33
|
+
else
|
34
|
+
v
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def typecast_date k, v
|
39
|
+
if v[/\d\d(\/|-)\d\d(\/|-)\d\d\d\d/]
|
40
|
+
Date.parse v
|
41
|
+
elsif v[/\d\d\d\d(\/|-)\d\d(\/|-)\d\d/]
|
42
|
+
Date.parse v
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def typecast_datetime k, v
|
49
|
+
if v[/\d\d(\/|-)\d\d(\/|-)\d\d\d\d \d\d?:\d\d?:\d\d?/]
|
50
|
+
d,m,y,h,min,s = v.split(/[-:\s\/]/)
|
51
|
+
Time.utc(y,m,d,h,min,s)
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def typecast_attachment k, v
|
58
|
+
attached = self.attachment k
|
59
|
+
if WebUtils.blank? v
|
60
|
+
attached.delete_all
|
61
|
+
return nil
|
62
|
+
elsif v.is_a?(Hash)&&v.key?(:tempfile)
|
63
|
+
return attached.create v
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
module DocumentMixins
|
3
|
+
module Validation
|
4
|
+
|
5
|
+
attr_accessor :_errors
|
6
|
+
|
7
|
+
def errors; self._errors; end
|
8
|
+
|
9
|
+
def error_on k,v
|
10
|
+
self._errors[k] = (self._errors[k]||[]) << v
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
self._errors = {}
|
16
|
+
exec_callback :before_validate
|
17
|
+
validate
|
18
|
+
exec_callback :after_validate
|
19
|
+
nested_docs.reduce self._errors.empty? do |result,d|
|
20
|
+
result &= d.valid?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate; end
|
25
|
+
|
26
|
+
def error_report
|
27
|
+
report = self._errors.dup || {}
|
28
|
+
persistent_instance_variables.each do |var|
|
29
|
+
value = instance_variable_get var
|
30
|
+
if is_nested_docs?(value)
|
31
|
+
k = var[1..-1].to_sym
|
32
|
+
report[k] = []
|
33
|
+
value.each do |d|
|
34
|
+
report[k] << d.error_report
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
report
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|