my_annotations 0.5.0 → 0.5.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.
Files changed (72) hide show
  1. data/.gitignore +6 -0
  2. data/.rvmrc +1 -0
  3. data/AUTHORS.rdoc +5 -0
  4. data/CHANGELOG.rdoc +64 -0
  5. data/INDEX.rdoc +17 -0
  6. data/generators/annotations_migration/annotations_migration_generator.rb +32 -0
  7. data/generators/annotations_migration/templates/migration_v1.rb +60 -0
  8. data/generators/annotations_migration/templates/migration_v2.rb +9 -0
  9. data/generators/annotations_migration/templates/migration_v3.rb +74 -0
  10. data/generators/annotations_migration/templates/migration_v4.rb +13 -0
  11. data/install.rb +1 -0
  12. data/lib/annotations/acts_as_annotatable.rb +271 -0
  13. data/lib/annotations/acts_as_annotation_source.rb +117 -0
  14. data/lib/annotations/acts_as_annotation_value.rb +115 -0
  15. data/lib/annotations/config.rb +148 -0
  16. data/lib/annotations/routing.rb +8 -0
  17. data/lib/annotations/util.rb +82 -0
  18. data/lib/annotations_version_fu.rb +119 -0
  19. data/lib/app/controllers/annotations_controller.rb +162 -0
  20. data/lib/app/controllers/application_controller.rb +2 -0
  21. data/lib/app/helpers/application_helper.rb +2 -0
  22. data/lib/app/models/annotation.rb +413 -0
  23. data/lib/app/models/annotation_attribute.rb +37 -0
  24. data/lib/app/models/annotation_value_seed.rb +48 -0
  25. data/lib/app/models/number_value.rb +23 -0
  26. data/lib/app/models/text_value.rb +23 -0
  27. data/my_annotations.gemspec +4 -9
  28. data/rails/init.rb +8 -0
  29. data/test/acts_as_annotatable_test.rb +186 -0
  30. data/test/acts_as_annotation_source_test.rb +84 -0
  31. data/test/acts_as_annotation_value_test.rb +17 -0
  32. data/test/annotation_attribute_test.rb +22 -0
  33. data/test/annotation_test.rb +213 -0
  34. data/test/annotation_value_seed_test.rb +14 -0
  35. data/test/annotation_version_test.rb +39 -0
  36. data/test/annotations_controller_test.rb +27 -0
  37. data/test/app_root/app/controllers/application_controller.rb +9 -0
  38. data/test/app_root/app/models/book.rb +5 -0
  39. data/test/app_root/app/models/chapter.rb +5 -0
  40. data/test/app_root/app/models/group.rb +3 -0
  41. data/test/app_root/app/models/tag.rb +6 -0
  42. data/test/app_root/app/models/user.rb +3 -0
  43. data/test/app_root/app/views/annotations/edit.html.erb +12 -0
  44. data/test/app_root/app/views/annotations/index.html.erb +1 -0
  45. data/test/app_root/app/views/annotations/new.html.erb +11 -0
  46. data/test/app_root/app/views/annotations/show.html.erb +3 -0
  47. data/test/app_root/config/boot.rb +115 -0
  48. data/test/app_root/config/environment.rb +16 -0
  49. data/test/app_root/config/environments/mysql.rb +0 -0
  50. data/test/app_root/config/routes.rb +4 -0
  51. data/test/app_root/db/migrate/001_create_test_models.rb +38 -0
  52. data/test/app_root/db/migrate/002_annotations_migration_v1.rb +60 -0
  53. data/test/app_root/db/migrate/003_annotations_migration_v2.rb +9 -0
  54. data/test/app_root/db/migrate/004_annotations_migration_v3.rb +72 -0
  55. data/test/config_test.rb +383 -0
  56. data/test/fixtures/annotation_attributes.yml +49 -0
  57. data/test/fixtures/annotation_value_seeds.csv +16 -0
  58. data/test/fixtures/annotation_versions.yml +259 -0
  59. data/test/fixtures/annotations.yml +239 -0
  60. data/test/fixtures/books.yml +13 -0
  61. data/test/fixtures/chapters.yml +27 -0
  62. data/test/fixtures/groups.yml +7 -0
  63. data/test/fixtures/number_value_versions.csv +2 -0
  64. data/test/fixtures/number_values.csv +2 -0
  65. data/test/fixtures/text_value_versions.csv +35 -0
  66. data/test/fixtures/text_values.csv +35 -0
  67. data/test/fixtures/users.yml +8 -0
  68. data/test/number_value_version_test.rb +40 -0
  69. data/test/routing_test.rb +27 -0
  70. data/test/test_helper.rb +41 -0
  71. data/test/text_value_version_test.rb +40 -0
  72. metadata +77 -7
@@ -0,0 +1,413 @@
1
+ class Annotation < ActiveRecord::Base
2
+ include AnnotationsVersionFu
3
+
4
+ belongs_to :annotatable,
5
+ :polymorphic => true
6
+
7
+ belongs_to :source,
8
+ :polymorphic => true
9
+
10
+ belongs_to :value,
11
+ :polymorphic => true,
12
+ :autosave => true
13
+
14
+ belongs_to :attribute,
15
+ :class_name => "AnnotationAttribute",
16
+ :foreign_key => "attribute_id"
17
+
18
+ belongs_to :version_creator,
19
+ :class_name => Annotations::Config.user_model_name
20
+
21
+ before_validation :process_value_generation
22
+
23
+ validates_presence_of :source_type,
24
+ :source_id,
25
+ :annotatable_type,
26
+ :annotatable_id,
27
+ :attribute_id,
28
+ :value_type
29
+
30
+ validate :check_annotatable,
31
+ :check_source,
32
+ :check_value,
33
+ :check_duplicate,
34
+ :check_limit_per_source,
35
+ :check_content_restrictions
36
+
37
+
38
+ # ========================
39
+ # Versioning configuration
40
+ # ------------------------
41
+
42
+ annotations_version_fu do
43
+ belongs_to :annotatable,
44
+ :polymorphic => true
45
+
46
+ belongs_to :source,
47
+ :polymorphic => true
48
+
49
+ belongs_to :value,
50
+ :polymorphic => true
51
+
52
+ belongs_to :attribute,
53
+ :class_name => "AnnotationAttribute",
54
+ :foreign_key => "attribute_id"
55
+
56
+ belongs_to :version_creator,
57
+ :class_name => "::#{Annotations::Config.user_model_name}"
58
+
59
+ validates_presence_of :source_type,
60
+ :source_id,
61
+ :annotatable_type,
62
+ :annotatable_id,
63
+ :attribute_id,
64
+ :value_type
65
+
66
+ # NOTE: make sure to update the logic in here
67
+ # if Annotation#value_content changes!
68
+ def value_content
69
+ self.value.nil? ? "" : self.value.ann_content
70
+ end
71
+
72
+ end
73
+
74
+ # ========================
75
+
76
+ # Named scope to allow you to include the value records too.
77
+ # Use this to *potentially* improve performance.
78
+ named_scope :include_values, lambda {
79
+ { :include => [ :value ] }
80
+ }
81
+
82
+ # Finder to get all annotations by a given source.
83
+ named_scope :by_source, lambda { |source_type, source_id|
84
+ { :conditions => { :source_type => source_type,
85
+ :source_id => source_id },
86
+ :order => "created_at DESC" }
87
+ }
88
+
89
+ # Finder to get all annotations for a given annotatable.
90
+ named_scope :for_annotatable, lambda { |annotatable_type, annotatable_id|
91
+ { :conditions => { :annotatable_type => annotatable_type,
92
+ :annotatable_id => annotatable_id },
93
+ :order => "created_at DESC" }
94
+ }
95
+
96
+ # Finder to get all annotations with a given attribute_name.
97
+ named_scope :with_attribute_name, lambda { |attrib_name|
98
+ { :conditions => { :annotation_attributes => { :name => attrib_name } },
99
+ :joins => :attribute,
100
+ :order => "created_at DESC" }
101
+ }
102
+
103
+ # Finder to get all annotations with one of the given attribute_names.
104
+ named_scope :with_attribute_names, lambda { |attrib_names|
105
+ conditions = [attrib_names.collect{"annotation_attributes.name = ?"}.join(" or ")] | attrib_names
106
+ { :conditions => conditions,
107
+ :joins => :attribute,
108
+ :order => "created_at DESC" }
109
+ }
110
+
111
+ # Finder to get all annotations for a given value_type.
112
+ named_scope :with_value_type, lambda { |value_type|
113
+ { :conditions => { :value_type => value_type },
114
+ :order => "created_at DESC" }
115
+ }
116
+
117
+ # Helper class method to look up an annotatable object
118
+ # given the annotatable class name and ID.
119
+ def self.find_annotatable(annotatable_type, annotatable_id)
120
+ return nil if annotatable_type.nil? or annotatable_id.nil?
121
+ begin
122
+ return annotatable_type.constantize.find(annotatable_id)
123
+ rescue
124
+ return nil
125
+ end
126
+ end
127
+
128
+ # Helper class method to look up a source object
129
+ # given the source class name and ID.
130
+ def self.find_source(source_type, source_id)
131
+ return nil if source_type.nil? or source_id.nil?
132
+ begin
133
+ return source_type.constantize.find(source_id)
134
+ rescue
135
+ return nil
136
+ end
137
+ end
138
+
139
+ def attribute_name
140
+ self.attribute.name
141
+ end
142
+
143
+ def attribute_name=(attr_name)
144
+ attr_name = attr_name.to_s.strip
145
+ self.attribute = AnnotationAttribute.find_or_create_by_name(attr_name)
146
+ end
147
+
148
+ alias_method :original_set_value=, :value=
149
+ def value=(value_in)
150
+ # Store this raw value in a temporary variable for
151
+ # later processing before the object is saved.
152
+ @raw_value = value_in
153
+ end
154
+
155
+ def value_content
156
+ self.value.nil? ? "" : self.value.ann_content
157
+ end
158
+
159
+ def self.create_multiple(params, separator)
160
+ success = true
161
+ annotations = [ ]
162
+ errors = [ ]
163
+
164
+ annotatable = Annotation.find_annotatable(params[:annotatable_type], params[:annotatable_id])
165
+
166
+ if annotatable
167
+ values = params[:value]
168
+
169
+ # Remove value from params hash
170
+ params.delete("value")
171
+
172
+ values.split(separator).each do |val|
173
+ ann = Annotation.new(params)
174
+ ann.value = val.strip
175
+
176
+ if ann.save
177
+ annotations << ann
178
+ else
179
+ error_text = "Error(s) occurred whilst saving annotation with attribute: '#{params[:attribute_name]}', and value: #{val} - #{ann.errors.full_messages.to_sentence}."
180
+ errors << error_text
181
+ logger.info(error_text)
182
+ success = false
183
+ end
184
+ end
185
+ else
186
+ errors << "Annotatable object doesn't exist"
187
+ success = false
188
+ end
189
+
190
+ return [ success, annotations, errors ]
191
+ end
192
+
193
+ protected
194
+
195
+ def ok_value_object_type?
196
+ return !self.value.nil? &&
197
+ self.value.is_a?(ActiveRecord::Base) &&
198
+ self.value.class.respond_to?(:is_annotation_value)
199
+ end
200
+
201
+ def process_value_generation
202
+ if defined?(@raw_value) && !@raw_value.blank?
203
+ val = process_value_adjustments(@raw_value)
204
+ val = try_use_value_factory(val)
205
+
206
+ # Now run default value generation logic
207
+ # (as a fallback for default cases)
208
+ case val
209
+ when String, Symbol
210
+ val = TextValue.new :text => val.to_s
211
+ when Numeric
212
+ val = NumberValue.new :number => val
213
+ when ActiveRecord::Base
214
+ # Do nothing
215
+ else
216
+ self.errors.add(:value, "is not a valid value object")
217
+ end
218
+
219
+ # Set it on the ActiveRecord level now
220
+ self.original_set_value = val
221
+
222
+ # Reset the internal raw value too, in case this is rerun
223
+ @raw_value = val
224
+ end
225
+
226
+ return true
227
+ end
228
+
229
+ def process_value_adjustments(value_in)
230
+ value_out = value_in
231
+
232
+ attr_name = self.attribute_name.downcase
233
+
234
+ value_in = value_out.to_s if value_out.is_a?(Symbol)
235
+
236
+ # Make lowercase or uppercase if required
237
+ if value_out.is_a?(String)
238
+ if Annotations::Config::attribute_names_for_values_to_be_downcased.include?(attr_name)
239
+ value_out = value_out.downcase
240
+ end
241
+ if Annotations::Config::attribute_names_for_values_to_be_upcased.include?(attr_name)
242
+ value_out = value_out.upcase
243
+ end
244
+
245
+ # Apply strip text rules
246
+ Annotations::Config::strip_text_rules.each do |attr, strip_rules|
247
+ if attr_name == attr.downcase
248
+ if strip_rules.is_a? Array
249
+ strip_rules.each do |s|
250
+ value_out = value_out.gsub(s, '')
251
+ end
252
+ elsif strip_rules.is_a? String or strip_rules.is_a? Regexp
253
+ value_out = value_out.gsub(strip_rules, '')
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ return value_out
260
+ end
261
+
262
+ def try_use_value_factory(value_in)
263
+ attr_name = self.attribute_name.downcase
264
+
265
+ if Annotations::Config::value_factories.has_key?(attr_name)
266
+ return Annotations::Config::value_factories[attr_name].call(value_in)
267
+ else
268
+ return value_in
269
+ end
270
+ end
271
+
272
+ # ===========
273
+ # Validations
274
+ # -----------
275
+
276
+ def check_annotatable
277
+ if Annotation.find_annotatable(self.annotatable_type, self.annotatable_id).nil?
278
+ self.errors.add(:annotatable_id, "doesn't exist")
279
+ return false
280
+ else
281
+ return true
282
+ end
283
+ end
284
+
285
+ def check_source
286
+ if Annotation.find_source(self.source_type, self.source_id).nil?
287
+ self.errors.add(:source_id, "doesn't exist")
288
+ return false
289
+ else
290
+ return true
291
+ end
292
+ end
293
+
294
+ def check_value
295
+ ok = true
296
+ if self.value.nil?
297
+ self.errors.add(:value, "object must be provided")
298
+ ok = false
299
+ elsif !ok_value_object_type?
300
+ self.errors.add(:value, "must be a valid annotation value object")
301
+ ok = false
302
+ else
303
+ attr_name = self.attribute_name.downcase
304
+ if Annotations::Config::valid_value_types.has_key?(attr_name) &&
305
+ !([ Annotations::Config::valid_value_types[attr_name] ].flatten.include?(self.value.class.name))
306
+ self.errors.add_to_base("Annotation value is of an invalid type for attribute name: '#{attr_name}'. Provided value is a #{self.value.class.name}.")
307
+ ok = false
308
+ end
309
+ end
310
+
311
+ return ok
312
+ end
313
+
314
+ # This method checks whether duplicates are allowed for this particular annotation type (ie:
315
+ # for the attribute that this annotation belongs to).
316
+ # If not allowed, it checks for a duplicate existing annotation and fails if one does exist.
317
+ def check_duplicate
318
+ if ok_value_object_type?
319
+ attr_name = self.attribute_name.downcase
320
+ if Annotations::Config.attribute_names_to_allow_duplicates.include?(attr_name)
321
+ return true
322
+ else
323
+ if self.value.class.has_duplicate_annotation?(self)
324
+ self.errors.add_to_base("This annotation already exists and is not allowed to be created again.")
325
+ return false
326
+ else
327
+ return true
328
+ end
329
+ end
330
+ end
331
+ end
332
+
333
+ # This method uses the 'limits_per_source config' setting to check whether a limit has been reached.
334
+ #
335
+ # NOTE: this check is only carried out on new records, not records that are being updated.
336
+ def check_limit_per_source
337
+ attr_name = self.attribute_name.downcase
338
+ if self.new_record? and Annotations::Config::limits_per_source.has_key?(attr_name)
339
+ options = Annotations::Config::limits_per_source[attr_name]
340
+ max = options[0]
341
+ can_replace = options[1]
342
+
343
+ unless (found_annotatable = Annotation.find_annotatable(self.annotatable_type, self.annotatable_id)).nil?
344
+ anns = found_annotatable.annotations_with_attribute_and_by_source(attr_name, self.source)
345
+
346
+ if anns.length >= max
347
+ self.errors.add_to_base("The limit has been reached for annotations with this attribute and by this source.")
348
+ return false
349
+ else
350
+ return true
351
+ end
352
+ else
353
+ return true
354
+ end
355
+ else
356
+ return true
357
+ end
358
+ end
359
+
360
+ def check_content_restrictions
361
+ if ok_value_object_type?
362
+ attr_name = self.attribute_name.downcase
363
+ content_to_check = self.value_content
364
+ if Annotations::Config::content_restrictions.has_key?(attr_name)
365
+ options = Annotations::Config::content_restrictions[attr_name]
366
+
367
+ case options[:in]
368
+ when Array
369
+ if content_to_check.is_a?(String)
370
+ if options[:in].map{|s| s.downcase}.include?(content_to_check.downcase)
371
+ return true
372
+ else
373
+ self.errors.add_to_base(options[:error_message])
374
+ return false
375
+ end
376
+ else
377
+ if options[:in].include?(content_to_check)
378
+ return true
379
+ else
380
+ self.errors.add_to_base(options[:error_message])
381
+ return false
382
+ end
383
+ end
384
+
385
+ when Range
386
+ # Need to take into account that "a_string".to_i == 0
387
+ if content_to_check == "0"
388
+ if options[:in] === 0
389
+ return true
390
+ else
391
+ self.errors.add_to_base(options[:error_message])
392
+ return false
393
+ end
394
+ else
395
+ if options[:in] === content_to_check.to_i
396
+ return true
397
+ else
398
+ self.errors.add_to_base(options[:error_message])
399
+ return false
400
+ end
401
+ end
402
+
403
+ else
404
+ return true
405
+ end
406
+ else
407
+ return true
408
+ end
409
+ end
410
+ end
411
+
412
+ # ===========
413
+ end
@@ -0,0 +1,37 @@
1
+ class AnnotationAttribute < ActiveRecord::Base
2
+ validates_presence_of :name,
3
+ :identifier
4
+
5
+ validates_uniqueness_of :name,
6
+ :case_sensitive => false
7
+
8
+ validates_uniqueness_of :identifier,
9
+ :case_sensitive => false
10
+
11
+ has_many :annotations,
12
+ :foreign_key => "attribute_id"
13
+
14
+ # If the identifier is not set, generate it before validation takes place.
15
+ # See Annotations::Config::default_attribute_identifier_template
16
+ # for more info.
17
+ #
18
+ # The rules are:
19
+ # - if an identifier is manually set, nothing happens.
20
+ # - if no identifier is set:
21
+ # - if name is enclosed in chevrons (eg: <http://...>) then the chevrons are taken out and the result is the new identifier.
22
+ # - if name is a URI beginning with http:// or urn: then this is used directly as the identifier.
23
+ # - in all other cases the identifier will be generated using the template specified by
24
+ # Annotations::Config::default_attribute_identifier_template, where '%s' in the template will be replaced with
25
+ # the transformation of 'name' through the Proc specified by Annotations::Config::attribute_name_transform_for_identifier.
26
+ def before_validation
27
+ unless self.name.blank? or !self.identifier.blank?
28
+ if self.name.match(/^<.+>$/)
29
+ self.identifier = self.name[1, self.name.length-1].chop
30
+ elsif self.name.match(/^http:\/\//) or self.name.match(/^urn:/)
31
+ self.identifier = self.name
32
+ else
33
+ self.identifier = (Annotations::Config::default_attribute_identifier_template % Annotations::Config::attribute_name_transform_for_identifier.call(self.name))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ class AnnotationValueSeed < ActiveRecord::Base
2
+ validates_presence_of :attribute_id,
3
+ :value_type,
4
+ :value_id
5
+
6
+ belongs_to :value,
7
+ :polymorphic => true
8
+
9
+ belongs_to :attribute,
10
+ :class_name => "AnnotationAttribute",
11
+ :foreign_key => "attribute_id"
12
+
13
+ # Named scope to allow you to include the value records too.
14
+ # Use this to *potentially* improve performance.
15
+ named_scope :include_values, lambda {
16
+ { :include => [ :value ] }
17
+ }
18
+
19
+ # Finder to get all annotation value seeds with a given attrib_name.
20
+ named_scope :with_attribute_name, lambda { |attrib_name|
21
+ { :conditions => { :annotation_attributes => { :name => attrib_name } },
22
+ :joins => :attribute,
23
+ :order => "created_at DESC" }
24
+ }
25
+
26
+ # Finder to get all annotation value seeds with one of the given attrib_names.
27
+ named_scope :with_attribute_names, lambda { |attrib_names|
28
+ conditions = [attrib_names.collect{"annotation_attributes.name = ?"}.join(" or ")] | attrib_names
29
+ { :conditions => conditions,
30
+ :joins => :attribute,
31
+ :order => "created_at DESC" }
32
+ }
33
+
34
+ # Finder to get all annotations for a given value_type.
35
+ named_scope :with_value_type, lambda { |value_type|
36
+ { :conditions => { :value_type => value_type },
37
+ :order => "created_at DESC" }
38
+ }
39
+
40
+ def self.find_by_attribute_name(attr_name)
41
+ return [] if attr_name.blank?
42
+
43
+ AnnotationValueSeed.find(:all,
44
+ :joins => [ :attribute ],
45
+ :conditions => { :annotation_attributes => { :name => attr_name } },
46
+ :order => "created_at DESC")
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ class NumberValue < ActiveRecord::Base
2
+ include AnnotationsVersionFu
3
+
4
+ validates_presence_of :number
5
+
6
+ acts_as_annotation_value :content_field => :number
7
+
8
+ belongs_to :version_creator,
9
+ :class_name => "::#{Annotations::Config.user_model_name}"
10
+
11
+ # ========================
12
+ # Versioning configuration
13
+ # ------------------------
14
+
15
+ annotations_version_fu do
16
+ validates_presence_of :number
17
+
18
+ belongs_to :version_creator,
19
+ :class_name => "::#{Annotations::Config.user_model_name}"
20
+ end
21
+
22
+ # ========================
23
+ end
@@ -0,0 +1,23 @@
1
+ class TextValue < ActiveRecord::Base
2
+ include AnnotationsVersionFu
3
+
4
+ validates_presence_of :text
5
+
6
+ acts_as_annotation_value :content_field => :text
7
+
8
+ belongs_to :version_creator,
9
+ :class_name => "::#{Annotations::Config.user_model_name}"
10
+
11
+ # ========================
12
+ # Versioning configuration
13
+ # ------------------------
14
+
15
+ annotations_version_fu do
16
+ validates_presence_of :text
17
+
18
+ belongs_to :version_creator,
19
+ :class_name => "::#{Annotations::Config.user_model_name}"
20
+ end
21
+
22
+ # ========================
23
+ end
@@ -1,19 +1,14 @@
1
+ require 'rake'
2
+
1
3
  Gem::Specification.new do |s|
2
4
  s.name = 'my_annotations'
3
- s.version = '0.5.0'
5
+ s.version = '0.5.1'
4
6
  s.date = '2013-05-02'
5
7
  s.summary = "This gem allows arbitrary metadata and relationships to be stored and retrieved, in the form of Annotations for any model objects in your Ruby on Rails (v2.2+) application."
6
8
  s.description = "This gem allows arbitrary metadata and relationships to be stored and retrieved, in the form of Annotations for any model objects in your Ruby on Rails (v2.2+) application."
7
9
  s.authors = ["Jiten Bhagat","Stuart Owen","Quyen Nguyen"]
8
10
  s.email = 'nttqa22001@yahoo.com'
9
- s.files = ["lib/my_annotations.rb",
10
- "RakeFile",
11
- "VERSION.yml",
12
- "LICENSE",
13
- "script/console",
14
- "README.rdoc",
15
- "RUNNING_TESTS.rdoc",
16
- "my_annotations.gemspec"]
11
+ s.files = `git ls-files`.split($/)
17
12
  s.homepage = 'https://github.com/myGrid/annotations'
18
13
  s.require_paths = ["lib"]
19
14
  end
data/rails/init.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'my_annotations.rb'
2
+
3
+ # FIX for engines model reloading issue in development mode
4
+ if ENV['RAILS_ENV'] != 'production'
5
+ load_paths.each do |path|
6
+ ActiveSupport::Dependencies.autoload_once_paths.delete(path)
7
+ end
8
+ end