gorillib-model 0.0.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 (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