populate-me 0.0.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.
Files changed (43) hide show
  1. data/README.md +9 -0
  2. data/lib/populate_me/control/_public/css/main.css +180 -0
  3. data/lib/populate_me/control/_public/css/plugin.asmselect.css +63 -0
  4. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  5. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  6. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
  7. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
  8. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  9. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  10. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  11. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  12. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  13. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
  14. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
  15. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
  16. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
  17. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
  18. data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +430 -0
  19. data/lib/populate_me/control/_public/img/handle-pattern.png +0 -0
  20. data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
  21. data/lib/populate_me/control/_public/img/placeholder.png +0 -0
  22. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
  23. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
  24. data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
  25. data/lib/populate_me/control/_public/js/addon.timepicker.js +20 -0
  26. data/lib/populate_me/control/_public/js/jquery-ui.js +114 -0
  27. data/lib/populate_me/control/_public/js/jquery.js +167 -0
  28. data/lib/populate_me/control/_public/js/jquery.mustache.js +559 -0
  29. data/lib/populate_me/control/_public/js/main.js +144 -0
  30. data/lib/populate_me/control/_public/js/plugin.asmselect.js +407 -0
  31. data/lib/populate_me/control/_public/js/plugin.form.js +11 -0
  32. data/lib/populate_me/control/_public/js/plugin.quicksearch.js +1 -0
  33. data/lib/populate_me/control/_public/js/plugin.underwood.js +4 -0
  34. data/lib/populate_me/control/views/layout.erb +77 -0
  35. data/lib/populate_me/control.rb +193 -0
  36. data/lib/populate_me/mongo/backend_api_plug.rb +77 -0
  37. data/lib/populate_me/mongo/crushyform.rb +225 -0
  38. data/lib/populate_me/mongo/mutation.rb +266 -0
  39. data/lib/populate_me/mongo/plug.rb +132 -0
  40. data/lib/populate_me/mongo/stash.rb +119 -0
  41. data/lib/populate_me/mongo.rb +1 -0
  42. data/populate-me.gemspec +14 -0
  43. metadata +120 -0
@@ -0,0 +1,266 @@
1
+ module PopulateMe
2
+ module Mongo
3
+ module Mutation
4
+
5
+ # Most important MongoDB module
6
+ # It defines the ODM
7
+
8
+ def self.included(weak)
9
+ weak.extend(MutateClass)
10
+ weak.db = DB
11
+ weak.schema = BSON::OrderedHash.new
12
+ weak.relationships = BSON::OrderedHash.new
13
+ end
14
+
15
+ module MutateClass
16
+ attr_accessor :db, :schema, :relationships
17
+ attr_writer :label_column, :sorting_order
18
+
19
+ LABEL_COLUMNS = ['title', 'label', 'fullname', 'full_name', 'surname', 'lastname', 'last_name', 'name', 'firstname', 'first_name', 'login', 'caption', 'reference', 'file_name', 'body', '_id']
20
+ def label_column; @label_column ||= LABEL_COLUMNS.find{|c| @schema.keys.include?(c)||c=='_id'}; end
21
+ def foreign_key_name(plural=false); "id#{'s' if plural}_"+self.name; end
22
+ def human_name; self.name.gsub(/([A-Z])/, ' \1')[1..-1]; end
23
+ def human_plural_name; human_name+'s'; end
24
+ def collection; db[self.name]; end
25
+ def ref(id)
26
+ if id.is_a?(String)&&BSON::ObjectId.legal?(id)
27
+ id = BSON::ObjectId.from_string(id)
28
+ elsif !id.is_a?(BSON::ObjectId)
29
+ id = ''
30
+ end
31
+ {'_id'=>id}
32
+ end
33
+ def find(selector={},opts={})
34
+ opts = {:sort=>self.sorting_order}.update(opts)
35
+ collection.find(selector,opts).extend(CursorMutation)
36
+ end
37
+ def find_one(spec_or_object_id=nil,opts={})
38
+ opts = {:sort=>self.sorting_order}.update(opts)
39
+ item = collection.find_one(spec_or_object_id,opts)
40
+ item.nil? ? nil : self.new(item)
41
+ end
42
+ def count(opts={}); collection.count(opts); end
43
+
44
+ def sorting_order
45
+ @sorting_order ||= if @schema.key?('position')&&!@schema['position'][:scope].nil?
46
+ [[@schema['position'][:scope], :asc], ['position', :asc]]
47
+ elsif @schema.key?('position')
48
+ [['position', :asc],['_id', :asc]]
49
+ else
50
+ ['_id', :asc]
51
+ end
52
+ end
53
+
54
+ def sort(id_list)
55
+ id_list.each_with_index do |id, position|
56
+ collection.update(ref(id), {'$set' => {'position'=>position}})
57
+ end
58
+ end
59
+
60
+ # CRUD
61
+ def get(id, opts={}); doc = collection.find_one(ref(id), opts); doc.nil? ? nil : self.new(doc); end
62
+ def delete(id); collection.remove(ref(id)); end
63
+
64
+ def is_unique(doc={})
65
+ return unless collection.count==0
66
+ doc = {'_id'=>BSON::ObjectId('000000000000000000000000')}.update(doc)
67
+ d = self.new
68
+ d.doc.update(doc)
69
+ d.save
70
+ end
71
+
72
+ private
73
+ def slot(name,opts={}); @schema[name] = {:type=>:string}.update(opts); end
74
+ def image_slot(name='image',opts={})
75
+ slot name, {:type=>:attachment}.update(opts)
76
+ slot "#{name}_tooltip"
77
+ slot "#{name}_alternative_text"
78
+ end
79
+ def has_many(k,opts={}); @relationships[k] = opts; end
80
+ end
81
+
82
+ # Instance Methods
83
+
84
+ attr_accessor :doc, :old_doc, :errors, :is_new
85
+ def initialize(document=nil); @errors={}; @doc = document || default_doc; end
86
+ def default_doc
87
+ @is_new = true
88
+ out = {}
89
+ model.schema.each { |k,v| out.store(k,v[:default].is_a?(Proc) ? v[:default].call : v[:default]) }
90
+ out
91
+ end
92
+ def model; self.class; end
93
+ def id; @doc['_id']; end
94
+ def [](field); @doc[field]; end
95
+ def []=(field,val); @doc[field] = val; end
96
+ def to_label; @doc[model.label_column].to_s.tr("\n\r", ' '); end
97
+ def to_param; "#{@doc['_id']}-#{to_label.scan(/\w+/).join('-')}"; end
98
+ def field_id_for(col); "%s-%s-%s" % [id||'new',model.name,col]; end
99
+
100
+ # relationships
101
+ def resolve_class(k); k.kind_of?(Class) ? k : Kernel.const_get(k); end
102
+ def parent(k, opts={})
103
+ if k.kind_of?(String)
104
+ key = k
105
+ klass = resolve_class(model.schema[k][:parent_class])
106
+ else
107
+ klass = resolve_class(k)
108
+ key = klass.foreign_key_name
109
+ end
110
+ klass.get(@doc[key], opts)
111
+ end
112
+ def slot_children(k, opts={})
113
+ if k.kind_of?(String)
114
+ key = k
115
+ klass = resolve_class(model.schema[k][:children_class])
116
+ else
117
+ klass = resolve_class(k)
118
+ key = klass.foreign_key_name(true)
119
+ end
120
+ ids = (@doc[key]||[]).map{|i| BSON::ObjectId.from_string(i) }
121
+ selector = {'_id'=>{'$in'=>ids}}
122
+ sort_proc = proc{ |a,b| ids.index(a['_id'])<=>ids.index(b['_id']) }
123
+ klass.find(selector, opts).to_a.sort(&sort_proc)
124
+ end
125
+ def first_slot_child(k, opts={})
126
+ if k.kind_of?(String)
127
+ key = k
128
+ klass = resolve_class(model.schema[k][:children_class])
129
+ else
130
+ klass = resolve_class(k)
131
+ key = klass.foreign_key_name(true)
132
+ end
133
+ klass.get((@doc[key]||[])[0], opts)
134
+ end
135
+ def children(k,opts={})
136
+ k = resolve_class(k)
137
+ slot_name = opts.delete(:slot_name) || model.foreign_key_name
138
+ k.find({slot_name=>@doc['_id'].to_s}, opts)
139
+ end
140
+ def first_child(k,opts={})
141
+ k = resolve_class(k)
142
+ slot_name = opts.delete(:slot_name) || model.foreign_key_name
143
+ d = k.find_one({slot_name=>@doc['_id'].to_s}, opts)
144
+ end
145
+ def children_count(k,sel={})
146
+ k = resolve_class(k)
147
+ slot_name = sel.delete(:slot_name) || model.foreign_key_name
148
+ k.collection.count(:query => {slot_name=>@doc['_id'].to_s}.update(sel))
149
+ end
150
+
151
+ # CRUD
152
+ def delete
153
+ before_delete
154
+ model.delete(@doc['_id'])
155
+ after_delete
156
+ end
157
+
158
+ # saving and hooks
159
+ def new?; @is_new ||= !@doc.key?('_id'); end
160
+ def update_doc(fields); @old_doc = @doc.dup; @doc.update(fields); @is_new = false; self; end
161
+ # Getter and setter in one
162
+ def errors_on(col,message=nil)
163
+ message.nil? ? @errors[col] : @errors[col] = (@errors[col]||[]) << message
164
+ end
165
+ def before_delete; @old_doc = @doc.dup; end
166
+ alias before_destroy before_delete
167
+ def after_delete
168
+ model.relationships.each do |k,v|
169
+ Kernel.const_get(k).find({model.foreign_key_name=>@old_doc['_id'].to_s}).each{|m| m.delete} unless v[:independent]
170
+ end
171
+ end
172
+ alias after_destroy after_delete
173
+ def valid?
174
+ before_validation
175
+ validate
176
+ after_validation
177
+ @errors.empty?
178
+ end
179
+ def before_validation
180
+ @errors = {}
181
+ @doc.each do |k,v|
182
+ next unless model.schema.key?(k)
183
+ type = k=='_id' ? :primary_key : model.schema[k][:type]
184
+ fix_method = "fix_type_#{type}"
185
+ if v==''
186
+ default = model.schema[k][:default]
187
+ @doc[k] = default.is_a?(Proc) ? default.call : default
188
+ else
189
+ self.__send__(fix_method, k, v) if self.respond_to?(fix_method)
190
+ end
191
+ end
192
+ end
193
+ def validate; end
194
+ def after_validation; end
195
+ def fix_type_integer(k,v); @doc[k] = v.to_i; end
196
+ def fix_type_boolean(k,v); @doc[k] = (v=='true'||v==true) ? true : false; end
197
+ def fix_type_date(k,v)
198
+ if v.is_a?(String)
199
+ if v[/\d\d\d\d-\d\d-\d\d/]
200
+ @doc[k] = ::Time.utc(*v.split('-'))
201
+ else
202
+ default = model.schema[k][:default]
203
+ @doc[k] = default.is_a?(Proc) ? default.call : default
204
+ end
205
+ end
206
+ end
207
+ def fix_type_datetime(k,v)
208
+ if v.is_a?(String)
209
+ if v[/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/]
210
+ @doc[k] = ::Time.utc(*v.split(/[-:\s]/))
211
+ else
212
+ default = model.schema[k][:default]
213
+ @doc[k] = default.is_a?(Proc) ? default.call : default
214
+ end
215
+ end
216
+ end
217
+
218
+ def save
219
+ return nil unless valid?
220
+ before_save
221
+ if new?
222
+ before_create
223
+ id = model.collection.insert(@doc)
224
+ @doc['_id'] = id
225
+ after_create
226
+ else
227
+ before_update
228
+ id = model.collection.update({'_id'=>@doc['_id']}, @doc)
229
+ after_update
230
+ end
231
+ after_save
232
+ id.nil? ? nil : self
233
+ end
234
+ def before_save; end
235
+ def before_create; end
236
+ def before_update; end
237
+ def after_save; end
238
+ def after_create; @is_new = false; end
239
+ def after_update; end
240
+
241
+ # ==========
242
+ # = Cursor =
243
+ # ==========
244
+ module CursorMutation
245
+ # Extend the cursor provided by the Ruby MongoDB driver
246
+ # We must keep the regular cursor
247
+ # so we should extend on demand.
248
+ # Meaning the cursor object should be extended, not the cursor class.
249
+ def self.extended(into)
250
+ into.set_mutant_class
251
+ end
252
+ def set_mutant_class
253
+ @mutant_class = Kernel.const_get(collection.name)
254
+ end
255
+ def next
256
+ n = super
257
+ n.nil? ? nil : @mutant_class.new(n)
258
+ end
259
+ # legacy
260
+ def each_mutant(&b); each(&b); end
261
+ def each_mutant_with_index(&b); each_with_index(&b); end
262
+ end
263
+
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,132 @@
1
+ require 'populate_me/mongo/mutation'
2
+ require 'populate_me/mongo/stash'
3
+ require 'populate_me/mongo/crushyform'
4
+ require 'populate_me/mongo/backend_api_plug'
5
+
6
+ module PopulateMe
7
+ module Mongo
8
+ module Plug
9
+
10
+ # This module is the one that plugs the model to the CMS
11
+
12
+ def self.included(base)
13
+ base.class_eval do
14
+ include PopulateMe::Mongo::Mutation
15
+ include PopulateMe::Mongo::Stash if (base.const_defined?(:WITH_STASH) && base::WITH_STASH)
16
+ include PopulateMe::Mongo::BackendApiPlug
17
+ include PopulateMe::Mongo::Crushyform
18
+ include InstanceMethods
19
+ end
20
+ base.extend(ClassMethods)
21
+ base.populate_config = {:nut_tree_class=>'sortable-grid'}
22
+ end
23
+
24
+ module ClassMethods
25
+
26
+ attr_accessor :populate_config
27
+
28
+ # def list_view(r)
29
+ # @list_options = {:request=>r, :destination=>r.fullpath, :path=>r.script_name, :filter=>r['filter'] }
30
+ # @list_options.store(:sortable,sortable_on_that_page?)
31
+ # out = list_view_header
32
+ # out << many_to_many_picker unless populate_config[:minilist_class].nil?
33
+ # out << "<ul class='nut-tree #{'sortable' if @list_options[:sortable]} #{populate_config[:nut_tree_class]}' id='#{self.name}' rel='#{@list_options[:path]}/#{self.name}'>"
34
+ # self.find(@list_options[:filter]||{}).each {|m| out << m.to_nutshell }
35
+ # out << "</ul>"
36
+ # end
37
+
38
+ def sortable_on_that_page?(r)
39
+ @schema.key?('position') && (@schema['position'][:scope].nil? || (r['filter']||{}).key?(@schema['position'][:scope]))
40
+ end
41
+
42
+ # def minilist_view
43
+ # o = "<ul class='minilist'>\n"
44
+ # self.collection.find.each_mutant do |m|
45
+ # thumb = m.respond_to?(:to_populate_thumb) ? m.to_populate_thumb('stash_thumb.gif') : m.placeholder_thumb('stash_thumb.gif')
46
+ # o << "<li title='#{m.to_label}' id='mini-#{m.id}'>#{thumb}<div>#{m.to_label}</div></li>\n"
47
+ # end
48
+ # o << "</ul>\n"
49
+ # end
50
+
51
+ private
52
+
53
+ def image_slot(name='image',opts={})
54
+ super(name,opts)
55
+ # First image slot is considered the best populate thumb by default
56
+ unless instance_methods.include?(:to_populate_thumb)
57
+ define_method :to_populate_thumb do |style|
58
+ generic_thumb(name, style)
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ module InstanceMethods
66
+
67
+ def after_stash(col)
68
+ convert(col, "-resize '100x75^' -gravity center -extent 100x75", 'stash_thumb.gif')
69
+ end
70
+
71
+ def generic_thumb(img , size='stash_thumb.gif', obj=self)
72
+ return placeholder_thumb(size) if obj.nil?
73
+ current = obj.doc[img]
74
+ if !current.nil? && !current[size].nil?
75
+ "/gridfs/#{current[size]}"
76
+ else
77
+ placeholder_thumb(size)
78
+ end
79
+ end
80
+
81
+ def placeholder_thumb(size)
82
+ "/_public/img/placeholder.#{size}"
83
+ end
84
+
85
+ def to_nutshell
86
+ {
87
+ 'class_name'=>model.name,
88
+ 'id'=>@doc['_id'].to_s,
89
+ 'foreign_key_name'=>model.foreign_key_name,
90
+ 'title'=>self.to_label,
91
+ 'thumb'=>self.respond_to?(:to_populate_thumb) ? self.to_populate_thumb('stash_thumb.gif') : nil,
92
+ 'children'=>nutshell_children,
93
+ }
94
+ end
95
+
96
+ def in_nutshell
97
+ o = model.list_options
98
+ out = "<div class='in-nutshell'>\n"
99
+ out << self.to_populate_thumb('nutshell.jpg') if self.respond_to?(:to_populate_thumb)
100
+ cols = model.populate_config[:quick_update_fields] || nutshell_backend_columns.select{|col|
101
+ [:boolean,:select].include?(model.schema[col][:type]) && !model.schema[col][:multiple] && !model.schema[col][:no_quick_update]
102
+ }
103
+ cols.each do |c|
104
+ column_label = c.to_s.sub(/^id_/, '').tr('_', ' ').capitalize
105
+ out << "<div class='quick-update'><form><span class='column-title'>#{column_label}:</span> #{self.crushyinput(c)}</form></div>\n"
106
+ end
107
+ out << "</div>\n"
108
+ end
109
+
110
+ def nutshell_backend_associations
111
+ model.relationships
112
+ end
113
+
114
+ def nutshell_children
115
+ nutshell_backend_associations.inject([]) do |arr, (k, opts)|
116
+ unless opts[:hidden]
117
+ klass = Kernel.const_get(k)
118
+ arr << {
119
+ 'children_class_name'=>k,
120
+ 'title'=>opts[:link_text] || "#{klass.human_name}(s)",
121
+ 'count'=>self.children_count(klass),
122
+ }
123
+ end
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
131
+ end
132
+
@@ -0,0 +1,119 @@
1
+ module PopulateMe
2
+ module Mongo
3
+ module Stash
4
+
5
+ # This module gives models the ability to deal with attachments
6
+
7
+ def self.included(base)
8
+ Stash.classes << base
9
+ base.extend(ClassMethods)
10
+ base.gridfs = GRID
11
+ end
12
+
13
+ module ClassMethods
14
+ attr_accessor :gridfs
15
+ end
16
+
17
+ # Instance Methods
18
+
19
+ def build_image_tag(col='image', style='original', html_attributes={})
20
+ return '' if @doc[col].nil?||@doc[col][style].nil?
21
+ title_field, alt_field = col+'_tooltip', col+'_alternative_text'
22
+ title = @doc[title_field] if model.schema.keys.include?(title_field)
23
+ alt = @doc[alt_field] if model.schema.keys.include?(alt_field)
24
+ html_attributes = {:src => "/gridfs/#{@doc[col][style]}", :title => title, :alt => alt}.update(html_attributes)
25
+ html_attributes = html_attributes.map do |k,v|
26
+ %{#{k}="#{model.html_escape(v.to_s)}"}
27
+ end.join(' ')
28
+ "<img #{html_attributes} />"
29
+ end
30
+
31
+ def fix_type_attachment(k,v)
32
+ if v=='nil'
33
+ delete_files_for(k) unless new?
34
+ @doc[k] = nil
35
+ elsif v.is_a?(Hash)&&v.key?(:tempfile)
36
+ delete_files_for(k) unless new?
37
+ @temp_attachments ||= {}
38
+ @temp_attachments[k] = v
39
+ attachment_id = model.gridfs.put(v[:tempfile], {:filename=>v[:filename], :content_type=>v[:type]})
40
+ @doc[k] = {'original'=>attachment_id}
41
+ end
42
+ end
43
+
44
+ def delete_files_for(col)
45
+ obj = (@old_doc||@doc)[col]
46
+ if obj.respond_to?(:each)
47
+ obj.each do |k,v|
48
+ model.gridfs.delete(v)
49
+ end
50
+ end
51
+ end
52
+
53
+ def after_delete
54
+ super
55
+ model.schema.each do |k,v|
56
+ delete_files_for(k) if v[:type]==:attachment
57
+ end
58
+ end
59
+
60
+ def after_save
61
+ super
62
+ unless @temp_attachments.nil?
63
+ @temp_attachments.each do |k,v|
64
+ after_stash(k)
65
+ end
66
+ end
67
+ end
68
+
69
+ def after_stash(col); end
70
+
71
+ def convert(col, convert_steps, style)
72
+ return if @doc[col].nil?
73
+ if @temp_attachments.nil? || @temp_attachments[col].nil?
74
+ f = GRID.get(@doc[col]['original'])
75
+ return unless f.content_type[/^image\//]
76
+ src = Tempfile.new('MongoStash_src')
77
+ src.binmode
78
+ src.write(f.read(4096)) until f.eof?
79
+ src.close
80
+ @temp_attachments ||= {}
81
+ @temp_attachments[col] ||= {}
82
+ @temp_attachments[col][:tempfile] = src
83
+ @temp_attachments[col][:type] = f.content_type
84
+ else
85
+ return unless @temp_attachments[col][:type][/^image\//]
86
+ src = @temp_attachments[col][:tempfile]
87
+ end
88
+ model.gridfs.delete(@doc[col][style]) unless @doc[col][style].nil?
89
+ ext = style[/\..*$/]
90
+ dest = Tempfile.new(['MongoStash_dest', ext])
91
+ dest.binmode
92
+ dest.close
93
+ system "convert \"#{src.path}\" #{convert_steps} \"#{dest.path}\""
94
+ filename = "#{model.name}/#{self.id}/#{style}"
95
+ content_type = Rack::Mime.mime_type(ext)
96
+ attachment_id = model.gridfs.put(dest.open, {:filename=>filename, :content_type=>content_type})
97
+ @doc[col] = @doc[col].update({style=>attachment_id})
98
+ model.collection.update({'_id'=>@doc['_id']}, @doc)
99
+ #src.close!
100
+ dest.close!
101
+ end
102
+
103
+ class << self
104
+ attr_accessor :classes
105
+ Stash.classes = []
106
+ def all_after_stash
107
+ Stash.classes.each do |m|
108
+ m.collection.find.each do |i|
109
+ m.schema.each do |k,v|
110
+ m.new(i).after_stash(k) if v[:type]==:attachment
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end