smooth_operator 0.4.4 → 1.2.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 (68) hide show
  1. checksums.yaml +9 -9
  2. data/.gitignore +2 -1
  3. data/.rspec +4 -0
  4. data/Gemfile +13 -0
  5. data/README.md +258 -10
  6. data/console.rb +44 -0
  7. data/lib/smooth_operator/array_with_meta_data.rb +31 -0
  8. data/lib/smooth_operator/attribute_assignment.rb +102 -0
  9. data/lib/smooth_operator/attribute_methods.rb +87 -0
  10. data/lib/smooth_operator/attributes/base.rb +107 -0
  11. data/lib/smooth_operator/attributes/dirty.rb +29 -0
  12. data/lib/smooth_operator/attributes/normal.rb +15 -0
  13. data/lib/smooth_operator/delegation.rb +60 -0
  14. data/lib/smooth_operator/finder_methods.rb +43 -0
  15. data/lib/smooth_operator/helpers.rb +79 -0
  16. data/lib/smooth_operator/model_schema.rb +81 -0
  17. data/lib/smooth_operator/open_struct.rb +37 -0
  18. data/lib/smooth_operator/operator.rb +145 -0
  19. data/lib/smooth_operator/operators/faraday.rb +75 -0
  20. data/lib/smooth_operator/operators/typhoeus.rb +77 -0
  21. data/lib/smooth_operator/persistence.rb +144 -0
  22. data/lib/smooth_operator/relation/array_relation.rb +13 -0
  23. data/lib/smooth_operator/relation/association_reflection.rb +75 -0
  24. data/lib/smooth_operator/relation/associations.rb +75 -0
  25. data/lib/smooth_operator/relation/reflection.rb +41 -0
  26. data/lib/smooth_operator/relation/single_relation.rb +14 -0
  27. data/lib/smooth_operator/remote_call/base.rb +80 -0
  28. data/lib/smooth_operator/remote_call/errors/connection_failed.rb +20 -0
  29. data/lib/smooth_operator/remote_call/errors/timeout.rb +20 -0
  30. data/lib/smooth_operator/remote_call/faraday.rb +19 -0
  31. data/lib/smooth_operator/remote_call/typhoeus.rb +19 -0
  32. data/lib/smooth_operator/serialization.rb +79 -0
  33. data/lib/smooth_operator/translation.rb +27 -0
  34. data/lib/smooth_operator/validations.rb +15 -0
  35. data/lib/smooth_operator/version.rb +1 -1
  36. data/lib/smooth_operator.rb +26 -5
  37. data/smooth_operator.gemspec +12 -3
  38. data/spec/factories/user_factory.rb +34 -0
  39. data/spec/require_helper.rb +11 -0
  40. data/spec/smooth_operator/attribute_assignment_spec.rb +351 -0
  41. data/spec/smooth_operator/attributes_dirty_spec.rb +53 -0
  42. data/spec/smooth_operator/delegation_spec.rb +139 -0
  43. data/spec/smooth_operator/finder_methods_spec.rb +105 -0
  44. data/spec/smooth_operator/model_schema_spec.rb +31 -0
  45. data/spec/smooth_operator/operator_spec.rb +46 -0
  46. data/spec/smooth_operator/persistence_spec.rb +424 -0
  47. data/spec/smooth_operator/remote_call_spec.rb +320 -0
  48. data/spec/smooth_operator/serialization_spec.rb +80 -0
  49. data/spec/smooth_operator/validations_spec.rb +42 -0
  50. data/spec/spec_helper.rb +25 -0
  51. data/spec/support/helpers/persistence_helper.rb +38 -0
  52. data/spec/support/localhost_server.rb +97 -0
  53. data/spec/support/models/address.rb +14 -0
  54. data/spec/support/models/comment.rb +3 -0
  55. data/spec/support/models/post.rb +13 -0
  56. data/spec/support/models/user.rb +41 -0
  57. data/spec/support/models/user_with_address_and_posts.rb +89 -0
  58. data/spec/support/test_server.rb +165 -0
  59. metadata +108 -18
  60. data/lib/smooth_operator/base.rb +0 -30
  61. data/lib/smooth_operator/core.rb +0 -218
  62. data/lib/smooth_operator/http_handlers/typhoeus/base.rb +0 -58
  63. data/lib/smooth_operator/http_handlers/typhoeus/orm.rb +0 -34
  64. data/lib/smooth_operator/http_handlers/typhoeus/remote_call.rb +0 -28
  65. data/lib/smooth_operator/operator/base.rb +0 -43
  66. data/lib/smooth_operator/operator/exceptions.rb +0 -64
  67. data/lib/smooth_operator/operator/orm.rb +0 -118
  68. data/lib/smooth_operator/operator/remote_call.rb +0 -84
@@ -0,0 +1,77 @@
1
+ require 'typhoeus'
2
+ require "smooth_operator/remote_call/typhoeus"
3
+
4
+ module SmoothOperator
5
+
6
+ module Operators
7
+
8
+ module Typhoeus
9
+
10
+ extend self
11
+
12
+ def make_the_call(http_verb, resource_path, params, body, options)
13
+ request = ::Typhoeus::Request.new *typhoeus_request_args(http_verb, resource_path, params, body, options)
14
+
15
+ hydra = options[:hydra] || ::Typhoeus::Hydra::hydra
16
+
17
+ _remote_call = nil
18
+
19
+ hydra.queue(request)
20
+
21
+ request.on_complete do |typhoeus_response|
22
+ _remote_call = remote_call(typhoeus_response)
23
+
24
+ yield(_remote_call) if block_given?
25
+ end
26
+
27
+ hydra.run if Helpers.blank?(options[:hydra])
28
+
29
+ _remote_call
30
+ end
31
+
32
+
33
+ protected ################ PROTECTED ################
34
+
35
+ def typhoeus_request_args(http_verb, relative_path, params, body, options)
36
+ [url(options, relative_path), build_typhoeus_options(http_verb, params, body, options)]
37
+ end
38
+
39
+ def remote_call(typhoeus_response)
40
+ if typhoeus_response.return_code == :couldnt_connect
41
+ RemoteCall::Errors::ConnectionFailed
42
+ elsif typhoeus_response.timed_out?
43
+ RemoteCall::Errors::Timeout
44
+ else
45
+ RemoteCall::Typhoeus
46
+ end.new(typhoeus_response)
47
+ end
48
+
49
+
50
+ private ################### PRIVATE ###############
51
+
52
+ def build_typhoeus_options(http_verb, params, body, options)
53
+ typhoeus_options = { method: http_verb, headers: options[:headers].merge({ "Content-type" => "application/x-www-form-urlencoded" }) }
54
+
55
+ typhoeus_options[:timeout] = options[:timeout] if Helpers.present?(options[:timeout])
56
+
57
+ typhoeus_options[:body] = body if Helpers.present?(body)
58
+
59
+ typhoeus_options[:params] = params if Helpers.present?(params)
60
+
61
+ typhoeus_options[:userpwd] = "#{options[:endpoint_user]}:#{options[:endpoint_pass]}" if Helpers.present?(options[:endpoint_user])
62
+
63
+ typhoeus_options
64
+ end
65
+
66
+ def url(options, relative_path)
67
+ url = options[:endpoint]
68
+
69
+ slice = url[-1] != '/' ? '/' : ''
70
+
71
+ url = "#{url}#{slice}#{relative_path}" if Helpers.present?(relative_path)
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,144 @@
1
+ module SmoothOperator
2
+ module Persistence
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ attr_reader :last_remote_call
9
+
10
+ def get_primary_key
11
+ get_internal_data(self.class.primary_key)
12
+ end
13
+
14
+ def reload(relative_path = nil, data = {}, options = {})
15
+ raise 'UnknownPath' if Helpers.blank?(relative_path) && (!respond_to?(self.class.primary_key) || Helpers.blank?(get_primary_key))
16
+
17
+ persistence_call(:reload, relative_path, data, options) do |remote_call|
18
+ block_given? ? yield(remote_call) : remote_call.status
19
+ end
20
+ end
21
+
22
+ def new_record?(bypass_cache = false)
23
+ return @new_record if !bypass_cache && defined?(@new_record)
24
+
25
+ @new_record = Helpers.blank?(get_primary_key)
26
+ end
27
+
28
+ def marked_for_destruction?(bypass_cache = false)
29
+ return @marked_for_destruction if !bypass_cache && defined?(@marked_for_destruction)
30
+
31
+ @marked_for_destruction = ["true", "1", true].include?(get_internal_data(self.class.destroy_key))
32
+ end
33
+
34
+ def destroyed?
35
+ return @destroyed if defined?(@destroyed)
36
+
37
+ @destroyed = false
38
+ end
39
+
40
+ def persisted?
41
+ !(new_record? || destroyed?)
42
+ end
43
+
44
+ def save(relative_path = nil, data = {}, options = {})
45
+ data = data_with_object_attributes(data, options)
46
+
47
+ if new_record?
48
+ create(relative_path, data, options) { |remote_call| block_given? ? yield(remote_call) : remote_call.status }
49
+ else
50
+ update(relative_path, data, options) { |remote_call| block_given? ? yield(remote_call) : remote_call.status }
51
+ end
52
+ end
53
+
54
+ def save!(relative_path = nil, data = {}, options = {})
55
+ save(relative_path, data, options) do |remote_call|
56
+ block_given? ? yield(remote_call) : remote_call.status
57
+ end || raise('RecordNotSaved')
58
+ end
59
+
60
+ def destroy(relative_path = nil, data = {}, options = {})
61
+ return false unless persisted?
62
+
63
+ persistence_call(:destroy, relative_path, data, options) do |remote_call|
64
+ @destroyed = true if remote_call.status
65
+
66
+ block_given? ? yield(remote_call) : remote_call.status
67
+ end
68
+ end
69
+
70
+
71
+ protected ######################### PROTECTED ##################
72
+
73
+ def create(relative_path, data, options)
74
+ persistence_call(:create, relative_path, data, options) do |remote_call|
75
+ @new_record = false if remote_call.status
76
+
77
+ block_given? ? yield(remote_call) : remote_call
78
+ end
79
+ end
80
+
81
+ def update(relative_path, data, options)
82
+ persistence_call(:update, relative_path, data, options) do |remote_call|
83
+ block_given? ? yield(remote_call) : remote_call
84
+ end
85
+ end
86
+
87
+ def persistence_call(method, relative_path, data, options)
88
+ options ||= {}
89
+
90
+ http_verb = options[:http_verb] || self.class.methods_vs_http_verbs[method]
91
+
92
+ make_the_call(http_verb, relative_path, data, options) do |remote_call|
93
+ @last_remote_call = remote_call
94
+
95
+ if !@last_remote_call.error? && @last_remote_call.parsed_response.is_a?(Hash)
96
+ assign_attributes @last_remote_call.parsed_response, from_server: true
97
+ end
98
+
99
+ yield(remote_call)
100
+ end
101
+ end
102
+
103
+ def data_with_object_attributes(data, options)
104
+ data = Helpers.stringify_keys(data)
105
+
106
+ hash = serializable_hash(options[:serializable_options]).dup
107
+
108
+ hash.delete(self.class.primary_key)
109
+
110
+ { self.class.resource_name => hash }.merge(data)
111
+ end
112
+
113
+
114
+ module ClassMethods
115
+
116
+ METHODS_VS_HTTP_VERBS = { reload: :get, create: :post, update: :put, destroy: :delete }
117
+
118
+ def methods_vs_http_verbs
119
+ Helpers.get_instance_variable(self, :methods_vs_http_verbs, METHODS_VS_HTTP_VERBS.dup)
120
+ end
121
+
122
+ def primary_key
123
+ Helpers.get_instance_variable(self, :primary_key, 'id')
124
+ end
125
+
126
+ attr_writer :primary_key
127
+
128
+ def destroy_key
129
+ Helpers.get_instance_variable(self, :destroy_key, '_destroy')
130
+ end
131
+
132
+ attr_writer :destroy_key
133
+
134
+ METHODS_VS_HTTP_VERBS.keys.each do |method|
135
+ define_method("#{method}_http_verb=") { |http_verb| methods_vs_http_verbs[method] = http_verb }
136
+ end
137
+
138
+ def create(attributes = nil, relative_path = nil, data = {}, options = {})
139
+ new(attributes).tap { |object| object.save(relative_path, data, options) }
140
+ end
141
+
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,13 @@
1
+ require "smooth_operator/relation/single_relation"
2
+
3
+ module SmoothOperator
4
+ module Relation
5
+ class ArrayRelation < SingleRelation
6
+
7
+ def reload2
8
+ "TODO2"
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,75 @@
1
+ require "smooth_operator/relation/reflection"
2
+
3
+ module SmoothOperator
4
+ module Relation
5
+ class AssociationReflection < Reflection
6
+
7
+ attr_reader :related_reflection, :macro
8
+
9
+ def initialize(association, related_reflection, options)
10
+ super(association, options)
11
+ @macro = options[:macro] || macro_default(association)
12
+ @related_reflection = related_reflection
13
+ end
14
+
15
+ def primary_key
16
+ @primary_key ||= options[:primary_key] || :id
17
+ end
18
+
19
+ def foreign_key
20
+ @foreign_key ||= options[:foreign_key] || foreign_key_default
21
+ end
22
+
23
+ def set_relational_keys(origin, destination)
24
+ return nil if options[:standalone] == true
25
+
26
+ if has_many? || has_one?
27
+ set_foreign_key(destination, primary_key_of(origin))
28
+ elsif belongs_to?
29
+ set_foreign_key(origin, primary_key_of(destination))
30
+ end
31
+ end
32
+
33
+ def set_foreign_key(object, id)
34
+ setter = "#{foreign_key}="
35
+
36
+ if object.respond_to?(setter)
37
+ object.send(setter, id)
38
+ elsif object.respond_to?("send_to_representative")
39
+ object.send_to_representative(setter, id)
40
+ end
41
+ end
42
+
43
+ def primary_key_of(object)
44
+ object.send(primary_key)
45
+ end
46
+
47
+ def has_many?
48
+ macro == :has_many
49
+ end
50
+
51
+ def has_one?
52
+ macro == :has_one
53
+ end
54
+
55
+ def belongs_to?
56
+ macro == :belongs_to
57
+ end
58
+
59
+ private ################################# private
60
+
61
+ def macro_default(association)
62
+ Helpers.plural?(association) ? :has_many : :belongs_to
63
+ end
64
+
65
+ def foreign_key_default
66
+ if has_many? || has_one?
67
+ "#{related_reflection.single_name}_id"
68
+ elsif belongs_to?
69
+ "#{single_name}_id"
70
+ end.to_sym
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,75 @@
1
+ require "smooth_operator/relation/array_relation"
2
+ require "smooth_operator/relation/association_reflection"
3
+
4
+ module SmoothOperator
5
+ module Relation
6
+ module Associations
7
+
8
+ def relations
9
+ @relations ||= {}
10
+ end
11
+
12
+ def get_relation(relation_name)
13
+ relations[relation_name] ||= self.class.build_relation(relation_name, get_internal_data(relation_name))
14
+ end
15
+
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ def has_many(nested_object_name, options = {})
23
+ accepts_nested_objects(nested_object_name, :has_many, options)
24
+ end
25
+
26
+ def has_one(nested_object_name, options = {})
27
+ accepts_nested_objects(nested_object_name, :has_one, options)
28
+ end
29
+
30
+ def belongs_to(nested_object_name, options = {})
31
+ accepts_nested_objects(nested_object_name, :belongs_to, options)
32
+ end
33
+
34
+ def reflections
35
+ Helpers.get_instance_variable(self, :reflections, {})
36
+ end
37
+
38
+ def reflect_on_association(association)
39
+ reflections[association]
40
+ end
41
+
42
+ def reflect_on_all_associations(macro = nil)
43
+ macro ? reflections.values.select { |reflection| reflection.macro == macro } : reflections.values
44
+ end
45
+
46
+ def build_relation(relation_name, data)
47
+ if reflections[relation_name.to_sym].has_many?
48
+ ArrayRelation.new(data || [], relation_name)
49
+ else
50
+ SingleRelation.new(data, relation_name)
51
+ end
52
+ end
53
+
54
+ protected ###################### PROTECTED ###################
55
+
56
+ def accepts_nested_objects(nested_object_name, macro, options = {})
57
+ default_options = { macro: macro }
58
+ options = options.is_a?(Hash) ? options.merge(default_options) : default_options
59
+ options = Helpers.symbolyze_keys(options)
60
+
61
+ reflection = AssociationReflection.new(nested_object_name, Reflection.new(name, {}), options)
62
+
63
+ self.send(:attr_accessor, "#{nested_object_name}_attributes".to_sym)
64
+ self.instance_variable_set("@reflections", reflections.merge(nested_object_name => reflection))
65
+
66
+ define_method("existing_#{nested_object_name}") { existing_nested_objects(nested_object_name) }
67
+ define_method("build_#{reflection.single_name}") { |attributes = {}, nested_object = nil| build_nested_object(nested_object_name, attributes, nested_object) }
68
+
69
+ schema(nested_object_name => reflection.klass)
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,41 @@
1
+ module SmoothOperator
2
+ module Relation
3
+ class Reflection
4
+
5
+ attr_reader :name, :klass, :options
6
+
7
+ def initialize(class_name, options)
8
+ options = options.is_a?(Hash) ? options : {}
9
+
10
+ @name, @options = class_name, options
11
+
12
+ @klass = options[:class_name] || klass_default(@name)
13
+
14
+ if options.include?(:class_name) && options[:class_name].nil?
15
+ @klass = nil
16
+ elsif @klass.is_a?(String)
17
+ @klass = @klass.constantize
18
+ end
19
+ end
20
+
21
+ def single_name
22
+ @single_name ||= options[:single_name] || name.to_s.singularize
23
+ end
24
+
25
+ def plural_name
26
+ @plural_name ||= options[:plural_name] || name.to_s.pluralize
27
+ end
28
+
29
+ private ################################# private
30
+
31
+ def klass_default(class_name)
32
+ if Helpers.plural?(class_name)
33
+ class_name.to_s.singularize.camelize
34
+ else
35
+ class_name.to_s.camelize
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ module SmoothOperator
2
+ module Relation
3
+ class SingleRelation < ::SimpleDelegator
4
+
5
+ attr_reader :relation_name
6
+
7
+ def initialize(object, relation_name)
8
+ @relation_name = relation_name
9
+ super(object)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,80 @@
1
+ module SmoothOperator
2
+
3
+ module RemoteCall
4
+
5
+ class Base
6
+
7
+ extend Forwardable
8
+
9
+ attr_reader :response, :http_status, :body, :headers
10
+
11
+ attr_accessor :object
12
+
13
+ def initialize(response)
14
+ @response = response
15
+ end
16
+
17
+
18
+ def ok?
19
+ http_status.between?(200, 299) || http_status == 304
20
+ end
21
+
22
+ def not_processed?
23
+ http_status == 422
24
+ end
25
+
26
+ def error?
27
+ !ok? && !not_processed?
28
+ end
29
+
30
+ def client_error?
31
+ http_status.between?(400, 499)
32
+ end
33
+
34
+ def server_error?
35
+ http_status.between?(500, 599) || http_status == 0
36
+ end
37
+
38
+
39
+ def not_found?
40
+ http_status == 404
41
+ end
42
+
43
+ def timeout?
44
+ false
45
+ end
46
+
47
+ def connection_failed?
48
+ false
49
+ end
50
+
51
+
52
+ def parsed_response
53
+ return nil if body.nil?
54
+
55
+ require 'json' unless defined? JSON
56
+
57
+ begin
58
+ JSON.parse(body)
59
+ rescue JSON::ParserError
60
+ nil
61
+ end
62
+ end
63
+
64
+ def status
65
+ error? ? nil : ok?
66
+ end
67
+
68
+ def objects
69
+ object.respond_to?(:length) ? object : []
70
+ end
71
+
72
+ def data
73
+ object.nil? ? parsed_response : object
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,20 @@
1
+ module SmoothOperator
2
+ module RemoteCall
3
+ module Errors
4
+
5
+ class ConnectionFailed < Base
6
+
7
+ def initialize(response)
8
+ super
9
+ @http_status = 0
10
+ end
11
+
12
+ def connection_failed?
13
+ true
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module SmoothOperator
2
+ module RemoteCall
3
+ module Errors
4
+
5
+ class Timeout < Base
6
+
7
+ def initialize(response)
8
+ super
9
+ @http_status = 0
10
+ end
11
+
12
+ def timeout?
13
+ true
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module SmoothOperator
2
+
3
+ module RemoteCall
4
+
5
+ class Faraday < Base
6
+
7
+ def initialize(response)
8
+ @response = response
9
+
10
+ @body = response.body
11
+ @headers = response.headers
12
+ @http_status = response.status
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,19 @@
1
+ module SmoothOperator
2
+
3
+ module RemoteCall
4
+
5
+ class Typhoeus < Base
6
+
7
+ def initialize(response)
8
+ @response = response
9
+
10
+ @body = response.body
11
+ @http_status = response.code
12
+ @headers = response.headers_hash
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,79 @@
1
+ module SmoothOperator
2
+
3
+ module Serialization
4
+
5
+ def to_hash(options = nil)
6
+ Helpers.symbolyze_keys(serializable_hash(options) || {})
7
+ end
8
+
9
+ # alias :attributes :to_hash
10
+ def attributes; to_hash; end
11
+
12
+ def to_json(options = nil)
13
+ require 'json' unless defined? JSON
14
+
15
+ JSON(serializable_hash(options) || {})
16
+ end
17
+
18
+ def read_attribute_for_serialization(attribute)
19
+ send(attribute)
20
+ end
21
+
22
+ def serializable_hash(options = nil)
23
+ hash = {}
24
+ options ||= {}
25
+
26
+ attribute_names(options).each do |attribute_name|
27
+ hash[attribute_name] = read_attribute_for_hashing(attribute_name, options)
28
+ end
29
+
30
+ method_names(options).each do |method_name|
31
+ hash[method_name.to_s] = send(method_name)
32
+ end
33
+
34
+ hash
35
+ end
36
+
37
+
38
+ protected ##################### PROTECTED ###################
39
+
40
+ # TODO: COMPLEX METHOD
41
+ def attribute_names(options)
42
+ attribute_names = internal_data.keys.sort
43
+
44
+ if only = options[:only]
45
+ attribute_names &= [*only].map(&:to_s)
46
+ elsif except = options[:except]
47
+ attribute_names -= [*except].map(&:to_s)
48
+ end
49
+
50
+ attribute_names
51
+ end
52
+
53
+ def method_names(options)
54
+ [*options[:methods]].select { |n| respond_to?(n) }
55
+ end
56
+
57
+ def read_attribute_for_hashing(attribute_name, options)
58
+ object = read_attribute_for_serialization(attribute_name)
59
+
60
+ _options = options[attribute_name] || options[attribute_name.to_sym]
61
+
62
+ if object.is_a?(Array)
63
+ object.map { |array_entry| attribute_to_hash(array_entry, _options) }
64
+ else
65
+ attribute_to_hash(object, _options)
66
+ end
67
+ end
68
+
69
+ def attribute_to_hash(object, options = nil)
70
+ if object.respond_to?(:serializable_hash)
71
+ Helpers.symbolyze_keys(object.serializable_hash(options))
72
+ else
73
+ object
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end