gorillib-model 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +12 -0
  3. data/README.md +21 -0
  4. data/Rakefile +15 -0
  5. data/gorillib-model.gemspec +27 -0
  6. data/lib/gorillib/builder.rb +239 -0
  7. data/lib/gorillib/core_ext/datetime.rb +23 -0
  8. data/lib/gorillib/core_ext/exception.rb +153 -0
  9. data/lib/gorillib/core_ext/module.rb +10 -0
  10. data/lib/gorillib/core_ext/object.rb +14 -0
  11. data/lib/gorillib/model/base.rb +273 -0
  12. data/lib/gorillib/model/collection/model_collection.rb +157 -0
  13. data/lib/gorillib/model/collection.rb +200 -0
  14. data/lib/gorillib/model/defaults.rb +115 -0
  15. data/lib/gorillib/model/errors.rb +24 -0
  16. data/lib/gorillib/model/factories.rb +555 -0
  17. data/lib/gorillib/model/field.rb +168 -0
  18. data/lib/gorillib/model/lint.rb +24 -0
  19. data/lib/gorillib/model/named_schema.rb +53 -0
  20. data/lib/gorillib/model/positional_fields.rb +35 -0
  21. data/lib/gorillib/model/schema_magic.rb +163 -0
  22. data/lib/gorillib/model/serialization/csv.rb +60 -0
  23. data/lib/gorillib/model/serialization/json.rb +44 -0
  24. data/lib/gorillib/model/serialization/lines.rb +30 -0
  25. data/lib/gorillib/model/serialization/to_wire.rb +54 -0
  26. data/lib/gorillib/model/serialization/tsv.rb +53 -0
  27. data/lib/gorillib/model/serialization.rb +41 -0
  28. data/lib/gorillib/model/type/extended.rb +83 -0
  29. data/lib/gorillib/model/type/ip_address.rb +153 -0
  30. data/lib/gorillib/model/type/url.rb +11 -0
  31. data/lib/gorillib/model/validate.rb +22 -0
  32. data/lib/gorillib/model/version.rb +5 -0
  33. data/lib/gorillib/model.rb +34 -0
  34. data/spec/builder_spec.rb +193 -0
  35. data/spec/core_ext/datetime_spec.rb +41 -0
  36. data/spec/core_ext/exception.rb +98 -0
  37. data/spec/core_ext/object.rb +45 -0
  38. data/spec/model/collection_spec.rb +290 -0
  39. data/spec/model/defaults_spec.rb +104 -0
  40. data/spec/model/factories_spec.rb +323 -0
  41. data/spec/model/lint_spec.rb +28 -0
  42. data/spec/model/serialization/csv_spec.rb +30 -0
  43. data/spec/model/serialization/tsv_spec.rb +28 -0
  44. data/spec/model/serialization_spec.rb +41 -0
  45. data/spec/model/type/extended_spec.rb +166 -0
  46. data/spec/model/type/ip_address_spec.rb +141 -0
  47. data/spec/model_spec.rb +261 -0
  48. data/spec/spec_helper.rb +15 -0
  49. data/spec/support/capture_output.rb +28 -0
  50. data/spec/support/nuke_constants.rb +9 -0
  51. data/spec/support/shared_context_for_builders.rb +59 -0
  52. data/spec/support/shared_context_for_models.rb +55 -0
  53. data/spec/support/shared_examples_for_factories.rb +71 -0
  54. data/spec/support/shared_examples_for_model_fields.rb +62 -0
  55. data/spec/support/shared_examples_for_models.rb +87 -0
  56. metadata +193 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .yardoc
2
+ Gemfile.lock
3
+ coverage/
4
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake', '~> 10.3.2'
7
+ gem 'yard', '~> 0.8.7'
8
+ gem 'rspec', '~> 2.14.1'
9
+ gem 'redcarpet', '~> 3.1.2', platform: [:ruby]
10
+ gem 'kramdown', '~> 1.4.0', platform: [:jruby]
11
+ gem 'simplecov', '~> 0.8.2', platform: [:ruby_19], require: false
12
+ end
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Gorillib::Model
2
+
3
+ ## WARNING
4
+
5
+ This has been extracted from the open-source library [gorillib](https://github.com/infochimps-labs/gorillib).
6
+ All functionality for `Gorillib::Model` has been retained, but all helper methods now come from `active_support`.
7
+ This separation from the old `gorillib` is intentional, as support for that library will no longer continue.
8
+ Please use this code only as intended, and make no assumptions about old functionality.
9
+
10
+ ## Usage
11
+
12
+ `require 'gorillib/model`
13
+
14
+ Gorillib has at least one powerful addition to the canon: the `Gorillib::Model` mixin.
15
+
16
+ Think of it like 'An ORM for JSON'. It's designed for data that spends as much time on the wire as it does in action -- things like API handlers or clients, data processing scripts, wukong jobs.
17
+
18
+ * lightweight
19
+ * serializes to/from JSON, TSV or plain hashes
20
+ * type converts when you need it, but doesn't complicate normal accessors
21
+ * upward compatible with ActiveModel
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ task default: [:spec]
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new
7
+
8
+ desc 'Run RSpec with code coverage'
9
+ task :cov do
10
+ ENV['GORILLIB_MODEL_COV'] = 'true'
11
+ Rake::Task[:spec].execute
12
+ end
13
+
14
+ require 'yard'
15
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gorillib/model/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'gorillib-model'
8
+ gem.version = Gorillib::Model::VERSION
9
+ gem.authors = %w[ Infochimps ]
10
+ gem.email = 'coders@infochimps.com'
11
+ gem.homepage = 'https://github.com/infochimps-labs/weavr.git'
12
+ gem.licenses = ['Apache 2.0']
13
+ gem.summary = 'Fully-featured Ruby model library'
14
+ gem.description = <<-DESC.gsub(/^ {4}/, '').chomp
15
+ Gorillib::Model
16
+ DESC
17
+
18
+ gem.files = `git ls-files`.split($/)
19
+ gem.test_files = gem.files.grep(/^spec/)
20
+ gem.require_paths = %w[ lib ]
21
+
22
+ gem.add_development_dependency('bundler', '~> 1.6.3')
23
+
24
+ gem.add_dependency('multi_json', '~> 1.10.1')
25
+ gem.add_dependency('activesupport', '~> 4.1.4')
26
+ gem.add_dependency('addressable', '~> 2.3.6')
27
+ end
@@ -0,0 +1,239 @@
1
+ require 'gorillib/model'
2
+ require 'gorillib/model/collection'
3
+ require 'gorillib/model/collection/model_collection'
4
+
5
+ module Gorillib
6
+ module Builder
7
+ extend ActiveSupport::Concern
8
+ include Gorillib::Model
9
+
10
+ # @return [Object, nil] the return value of the block, or nil if no block given
11
+ def receive!(*args, &block)
12
+ super(*args)
13
+ if block_given?
14
+ (block.arity == 1) ? block.call(self) : self.instance_eval(&block)
15
+ end
16
+ end
17
+
18
+ def getset(field, *args, &block)
19
+ ArgumentError.check_arity!(args, 0..1)
20
+ if args.empty?
21
+ read_attribute(field.name)
22
+ else
23
+ write_attribute(field.name, args.first)
24
+ end
25
+ end
26
+
27
+ def getset_member(field, *args, &block)
28
+ ArgumentError.check_arity!(args, 0..1)
29
+ attrs = args.first
30
+ if attrs.is_a?(field.type)
31
+ # actual object: assign it into field
32
+ val = attrs
33
+ write_attribute(field.name, val)
34
+ else
35
+ val = read_attribute(field.name)
36
+ if val.present?
37
+ # existing item: update it with args and block
38
+ val.receive!(*args, &block) if args.present? or block_given?
39
+ elsif attrs.blank? and not block_given?
40
+ # missing item (read): return nil
41
+ return nil
42
+ else
43
+ # missing item (write): construct item and add to collection
44
+ options = args.extract_options!.merge(:owner => self)
45
+ val = field.type.receive(*args, options, &block)
46
+ write_attribute(field.name, val)
47
+ end
48
+ end
49
+ val
50
+ end
51
+
52
+ def getset_collection_item(field, item_key, attrs={}, &block)
53
+ plural_name = field.plural_name
54
+ if attrs.is_a?(field.item_type)
55
+ # actual object: assign it into collection
56
+ val = attrs
57
+ set_collection_item(plural_name, item_key, val)
58
+ elsif has_collection_item?(plural_name, item_key)
59
+ # existing item: retrieve it, updating as directed
60
+ val = get_collection_item(plural_name, item_key)
61
+ val.receive!(attrs, &block)
62
+ else
63
+ # missing item: autovivify item and add to collection
64
+ val = field.item_type.receive({ key_method => item_key, :owner => self }.merge(attrs), &block)
65
+ set_collection_item(plural_name, item_key, val)
66
+ end
67
+ val
68
+ end
69
+
70
+ def get_collection_item(plural_name, item_key)
71
+ collection_of(plural_name)[item_key]
72
+ end
73
+
74
+ def set_collection_item(plural_name, item_key, item)
75
+ collection = collection_of(plural_name)
76
+ collection[item_key] = item
77
+ collection[item_key]
78
+ end
79
+
80
+ def has_collection_item?(plural_name, item_key)
81
+ collection_of(plural_name).include?(item_key)
82
+ end
83
+
84
+ def key_method
85
+ :name
86
+ end
87
+
88
+ def to_key
89
+ self.send(key_method)
90
+ end
91
+
92
+ def to_inspectable
93
+ super.tap{|attrs| attrs.delete(:owner) }
94
+ end
95
+
96
+ def collection_of(plural_name)
97
+ self.read_attribute(plural_name)
98
+ end
99
+
100
+ module ClassMethods
101
+ include Gorillib::Model::ClassMethods
102
+
103
+ def magic(field_name, type, options={})
104
+ field(field_name, type, {:field_type => ::Gorillib::Builder::GetsetField}.merge(options))
105
+ end
106
+ def member(field_name, type, options={})
107
+ field(field_name, type, {:field_type => ::Gorillib::Builder::MemberField}.merge(options))
108
+ end
109
+
110
+ # FIXME: this interface is borked -- it should not take the item_type in the second slot.
111
+ def collection(field_name, item_type, options={})
112
+ super(field_name, Gorillib::ModelCollection, {
113
+ :item_type => item_type, :field_type => ::Gorillib::Builder::GetsetCollectionField }.merge(options))
114
+ end
115
+
116
+ protected
117
+
118
+ def define_attribute_getset(field)
119
+ field_name = field.name; type = field.type
120
+ define_meta_module_method(field_name, field.visibility(:reader)) do |*args, &block|
121
+ begin
122
+ getset(field, *args, &block)
123
+ rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{args}'") rescue nil ; raise ; end
124
+ end
125
+ end
126
+
127
+ def define_member_getset(field)
128
+ field_name = field.name; type = field.type
129
+ define_meta_module_method(field_name, field.visibility(:reader)) do |*args, &block|
130
+ begin
131
+ getset_member(field, *args, &block)
132
+ rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{args}'") rescue nil ; raise ; end
133
+ end
134
+ end
135
+
136
+ def define_collection_getset(field)
137
+ field_name = field.name; item_type = field.item_type
138
+ define_meta_module_method(field.singular_name, field.visibility(:collection_getset)) do |*args, &block|
139
+ begin
140
+ getset_collection_item(field, *args, &block)
141
+ rescue StandardError => err ; err.polish("#{self.class}.#{field_name} c[#{item_type}] on #{args}'") rescue nil ; raise ; end
142
+ end
143
+ end
144
+
145
+ def define_collection_tester(field)
146
+ plural_name = field.plural_name
147
+ define_meta_module_method("has_#{field.singular_name}?", field.visibility(:collection_tester)) do |item_key|
148
+ begin
149
+ collection_of(plural_name).include?(item_key)
150
+ rescue StandardError => err ; err.polish("#{self.class}.#{plural_name} having #{item_key}?'") rescue nil ; raise ; end
151
+ end
152
+ end
153
+
154
+ end
155
+ end
156
+
157
+ module FancyBuilder
158
+ extend ActiveSupport::Concern
159
+ include Gorillib::Builder
160
+
161
+ included do |base|
162
+ base.magic :name, Symbol
163
+ end
164
+
165
+ module ClassMethods
166
+ include Gorillib::Builder::ClassMethods
167
+
168
+ def belongs_to(field_name, type, options={})
169
+ field = member(field_name, type)
170
+ define_meta_module_method "#{field.name}_name" do
171
+ val = getset_member(field) or return nil
172
+ val.name
173
+ end
174
+ field
175
+ end
176
+
177
+ def option(field_name, options={})
178
+ type = options.delete(:type){ Whatever }
179
+ magic(field_name, type)
180
+ end
181
+
182
+ def collects(type, clxn_name)
183
+ type_handle = type.handle
184
+ define_meta_module_method type_handle do |item_name, attrs={}, options={}, &block|
185
+ send(clxn_name, item_name, attrs, options.merge(:factory => type), &block)
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ module Builder
192
+
193
+ class GetsetField < Gorillib::Model::Field
194
+ self.visibilities = visibilities.merge(:writer => false, :tester => false, :getset => true)
195
+ def inscribe_methods(model)
196
+ model.__send__(:define_attribute_getset, self)
197
+ model.__send__(:define_attribute_writer, self.name, self.type, visibility(:writer))
198
+ model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
199
+ model.__send__(:define_attribute_receiver, self.name, self.type, visibility(:receiver))
200
+ end
201
+ end
202
+
203
+ class MemberField < Gorillib::Model::Field
204
+ self.visibilities = visibilities.merge(:writer => false, :tester => true)
205
+ def inscribe_methods(model)
206
+ model.__send__(:define_member_getset, self)
207
+ model.__send__(:define_attribute_writer, self.name, self.type, visibility(:writer))
208
+ model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
209
+ model.__send__(:define_attribute_receiver, self.name, self.type, visibility(:receiver))
210
+ end
211
+ end
212
+
213
+ class GetsetCollectionField < ::Gorillib::Model::SimpleCollectionField
214
+ field :singular_name, Symbol, :default => ->{ ActiveSupport::Inflector.singularize(name.to_s).to_sym }
215
+
216
+ self.visibilities = visibilities.merge(:writer => false, :tester => false,
217
+ :collection_getset => :public, :collection_tester => true)
218
+
219
+ alias_method :plural_name, :name
220
+ def singular_name
221
+ @singular_name ||= ActiveSupport::Inflector.singularize(name.to_s).to_sym
222
+ end
223
+
224
+ def inscribe_methods(model)
225
+ raise "Plural and singular names must differ: #{self.plural_name}" if (singular_name == plural_name)
226
+ #
227
+ @visibilities[:writer] = false
228
+ model.__send__(:define_attribute_reader, self.name, self.type, visibility(:reader))
229
+ model.__send__(:define_attribute_tester, self.name, self.type, visibility(:tester))
230
+ #
231
+ model.__send__(:define_collection_receiver, self)
232
+ model.__send__(:define_collection_getset, self)
233
+ model.__send__(:define_collection_tester, self)
234
+ end
235
+ end
236
+ CollectionField = GetsetCollectionField
237
+
238
+ end
239
+ end
@@ -0,0 +1,23 @@
1
+ Time.class_eval do
2
+ #
3
+ # Parses the time but never fails.
4
+ # Return value is always in the UTC time zone.
5
+ #
6
+ # A flattened datetime -- a 14-digit YYYYmmddHHMMMSS -- is fixed to the UTC
7
+ # time zone by parsing it as YYYYmmddHHMMMSSZ <- 'Z' at end
8
+ #
9
+ def self.parse_safely dt
10
+ return nil if dt.nil? || (dt.respond_to?(:empty) && dt.empty?)
11
+ begin
12
+ case
13
+ when dt.is_a?(Time) then dt.utc
14
+ when (dt.to_s =~ /\A\d{14}\z/) then parse(dt.to_s+'Z', true)
15
+ else parse(dt.to_s, true).utc
16
+ end
17
+ rescue StandardError => err
18
+ warn "Can't parse a #{self} from #{dt.inspect}"
19
+ warn err
20
+ return nil
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,153 @@
1
+ Exception.class_eval do
2
+ # @return [Array] file, line, method_name
3
+ def self.caller_parts(depth=1)
4
+ caller_line = caller(depth).first
5
+ mg = %r{\A([^:]+):(\d+):in \`([^\']+)\'\z}.match(caller_line) or return [caller_line, 1, '(unknown)']
6
+ [mg[1], mg[2].to_i, mg[3]]
7
+ rescue
8
+ warn "problem in #{self}.caller_parts"
9
+ return [__FILE__, __LINE__, '(unknown)']
10
+ end
11
+
12
+ #
13
+ # Add context to the backtrace of exceptions ocurring downstream from caller.
14
+ # This is expecially useful in metaprogramming. Follow the implementation in
15
+ # the example.
16
+ #
17
+ # @note !! Be sure to rescue the call to this method; few things suck worse
18
+ # than debugging your rescue blocks.
19
+ #
20
+ # @example
21
+ # define_method(:cromulate) do |level|
22
+ # begin
23
+ # adjust_cromulance(cromulator, level)
24
+ # rescue StandardError => err ; err.polish("setting cromulance #{level} for #{cromulator}") rescue nil ; raise ; end
25
+ # end
26
+ #
27
+ def polish(extra_info)
28
+ filename, _, method_name = self.class.caller_parts(2)
29
+ method_name.gsub!(/rescue in /, '')
30
+ most_recent_line = backtrace.detect{|line|
31
+ line.include?(filename) && line.include?(method_name) && line.end_with?("'") }
32
+ most_recent_line.sub!(/'$/, "' for [#{extra_info.to_s[0..300]}]")
33
+ end
34
+
35
+ end
36
+
37
+ ArgumentError.class_eval do
38
+ # Raise an error if there are a different number of arguments than expected.
39
+ # The message will have the same format used by Ruby internal methods.
40
+ # @see #arity_at_least!
41
+ #
42
+ # @example want `getset(:foo)` to be different from `getset(:foo, nil)`
43
+ # def getset(key, *args)
44
+ # ArgumentError.check_arity!(args, 0..1)
45
+ # return self[key] if args.empty?
46
+ # self[key] = args.first
47
+ # end
48
+ #
49
+ # @overload check_arity!(args, n)
50
+ # @param [Array] args splat args as handed to the caller
51
+ # @param [Integer] val expected length
52
+ # @overload check_arity!(args, x..y)
53
+ # @param [Array] args splat args as handed to the caller
54
+ # @param [#include?] val expected range/list/set of lengths
55
+ # @raise ArgumentError when there are
56
+ def self.check_arity!(args, val, &block)
57
+ allowed_arity = val.is_a?(Integer) ? (val..val) : val
58
+ return true if allowed_arity.include?(args.length)
59
+ info = " #{block.call}" rescue nil if block_given?
60
+ raise self.new("wrong number of arguments (#{args.length} for #{val})#{info}")
61
+ end
62
+
63
+ # Raise an error if there are fewer arguments than expected. The message will
64
+ # have the same format used by Ruby internal methods.
65
+ # @see #check_arity!
66
+ #
67
+ # @example want to use splat args, requiring at least one
68
+ # def assemble_path(*pathsegs)
69
+ # ArgumentError.arity_at_least!(pathsegs, 1)
70
+ # # ...
71
+ # end
72
+ #
73
+ # @param [Array] args splat args as handed to the caller
74
+ # @param [Integer] val minimum expected length
75
+ def self.arity_at_least!(args, min_arity)
76
+ check_arity!(args, min_arity .. Float::INFINITY)
77
+ end
78
+ end
79
+
80
+ class TypeMismatchError < ArgumentError ; end
81
+
82
+ class ArgumentError
83
+ #
84
+ # @param [Array[Symbol,Class,Module]] types
85
+ #
86
+ # @example simple
87
+ # TypeMismatchError.mismatched!(:foo)
88
+ # #=> "TypeMismatchError: :foo has mismatched type
89
+ #
90
+ # @example Can supply the types or duck-types that are expected:
91
+ # TypeMismatchError.mismatched!(:foo, [:to_str, Integer])
92
+ # #=> "TypeMismatchError: :foo has mismatched type; expected #to_str or Integer"
93
+ #
94
+ def self.mismatched!(obj, types=[], msg=nil, *args)
95
+ types = Array(types)
96
+ message = (obj.inspect rescue '(uninspectable object)')
97
+ message << " has mismatched type"
98
+ message << ': ' << msg if msg
99
+ unless types.empty?
100
+ message << '; expected ' << types.map{|type| type.is_a?(Symbol) ? "##{type}" : type.to_s }.join(" or ")
101
+ end
102
+ raise self, message, *args
103
+ end
104
+
105
+
106
+ def self.block_required!(block)
107
+ raise self.new("Block is required") unless block
108
+ end
109
+
110
+ #
111
+ # @param obj [Object] Object to check
112
+ # @param types [Array[Symbol,Class,Module]] Types or methods to compare
113
+ #
114
+ # @example simple
115
+ # TypeMismatchError.mismatched!(:foo)
116
+ # #=> "TypeMismatchError: :foo has mismatched type
117
+ #
118
+ # @example Can supply the types or duck-types that are expected:
119
+ # TypeMismatchError.mismatched!(:foo, [:to_str, Integer])
120
+ # #=> "TypeMismatchError: :foo has mismatched type; expected #to_str or Integer"
121
+ #
122
+ def self.check_type!(obj, types, *args)
123
+ types = Array(types)
124
+ return true if types.any? do |type|
125
+ case type
126
+ when Module then obj.is_a?(type)
127
+ when Symbol then obj.respond_to?(type)
128
+ else raise StandardError, "Can't check type #{type} -- this is an error in the call to the type-checker, not in the object the type-checker is checking"
129
+ end
130
+ end
131
+ self.mismatched!(obj, types, *args)
132
+ end
133
+
134
+ end
135
+
136
+ #
137
+ class AbstractMethodError < NoMethodError ; end
138
+
139
+ NoMethodError.class_eval do
140
+ MESSAGE_FMT = "undefined method `%s' for %s:%s"
141
+
142
+ # Raise an error with the same format used by Ruby internal methods
143
+ def self.undefined_method!(obj)
144
+ file, line, meth = caller_parts
145
+ raise self.new(MESSAGE_FMT % [meth, obj, obj.class])
146
+ end
147
+
148
+ def self.abstract_method!(obj)
149
+ file, line, meth = caller_parts
150
+ raise AbstractMethodError.new("#{MESSAGE} -- must be implemented by the subclass" % [meth, obj, obj.class])
151
+ end
152
+
153
+ end
@@ -0,0 +1,10 @@
1
+ # It is necessary to override this ActiveSupport method
2
+ # due to the differences between #remove_method and #undef_method
3
+ Module.class_eval do
4
+ def remove_possible_method(method)
5
+ if method_defined?(method) || private_method_defined?(method)
6
+ remove_method(method)
7
+ end
8
+ rescue NameError
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ Object.class_eval do
2
+ # Override this in a child if it cannot be dup'ed
3
+ #
4
+ # @return [Object]
5
+ def try_dup
6
+ self.dup
7
+ end
8
+ end
9
+
10
+ [ TrueClass, FalseClass, Module, NilClass, Numeric, Symbol ].each do |klass|
11
+ klass.class_eval do
12
+ def try_dup() self ; end
13
+ end
14
+ end