smooth_operator 1.11.2 → 1.20.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OGZkNjkyYmRiYThmY2U0MmIzMWRlN2FkYjMzYzBlMmQ0MTdkMWRjMA==
4
+ ZDBhM2U5NjlkNjVmMzk4MGQxNTVkNTBiZDBhODI5NDlkMjRkZGQ2OQ==
5
5
  data.tar.gz: !binary |-
6
- MzI4M2U5YmI5NzhlYjVkMjIwMGRiYzM2Y2IxNmNiYjI0NTIzYjQ5Yw==
6
+ ZTg1N2IwOTY0ZDlmMmVjMTVjNGFmMzNjM2YxZjE5MGFlMTNiMDk3Mg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZGU4MDc5MjcxZmUyMTg2N2RiNWVlMjBjMTgwNzcwODMxZWJiZDM3YjQ3OThl
10
- ODMyZDcyNDNhMzk0YjVkMmZlODZiZjg5NDZlMzY2MzA4Yzk5ZWI0YmViODMx
11
- Y2IwMGI0ZTQyZWFhMWU3MmJhNjg1Y2M3YzYxMjZkZDgyYWYwYWU=
9
+ MWQzYjFkMGNmNDY2NmU2MzFjOThjMWQzYWZjMjNiNjNjYTQwZDZkNGVlYzdm
10
+ YzIxODAyNWI4YTZiNjVhYjk2NzgzMGNjYjc1MGVhNWY0ZGRiNWNiZDQ5MGFl
11
+ Y2E3NGM2ZDNiNWFiYTg2YjMzNTVhMmNkODM0MTYxZTg2YzBmMTU=
12
12
  data.tar.gz: !binary |-
13
- YTIwYmQ1YTNjYzliZjJmOTg3YWEyNjczZWZhMWU1YzkwODM2NDJjZGM2YzU4
14
- MmNmOWQ2ZjZkNWRiMDQ0MDgxMTY1N2UxYzgxN2ZiNTVmNGFmNzQxOWM0N2M3
15
- MWFiY2I1ZThiOTM2MjI1MGI5NmIyZGY4YTU3YTk5YTEyMGJhN2U=
13
+ OGYwYjBiY2U2NWUzMThhZGIwOTZiOWMzOWZkYzM2NDZiODE4NmFkYWJmNzhj
14
+ NTI3MWU4NDVjOTU0ZTdkNTU1MWRiOTk3NzRmYmFjOGJkNjgyNzhlOTFjMzMx
15
+ NTgxNzUzZjQ1MDQ3MzNkNTJiYzNlM2E2NjUyN2ZiZWI3YjRmMmQ=
data/console.rb CHANGED
@@ -27,5 +27,8 @@ LocalhostServer.new(TestServer.new, 4567)
27
27
 
28
28
  # "[{\"patient_id\"=>33, \"messages\"=>[{\"id\"=>\"53722c20cb38247c36000003\", \"title\"=>\"Joao Goncalves\", \"created_at\"=>\"2014-05-13T14:28:48Z\"}, {\"id\"=>\"53722bfccb382485d5000002\", \"title\"=>\"Joao Goncalves\", \"created_at\"=>\"2014-05-13T14:28:12Z\"}, {\"id\"=>\"53722b91cb3824e913000001\", \"title\"=>\"Joao Goncalves\", \"created_at\"=>\"2014-05-13T14:26:25Z\"}]}]"
29
29
 
30
- binding.pry
30
+ post = Post.new(comments: [{ id: 1, name: '1' }, { id: 2, name: '2' }], address: { id: 1, name: 'address' })
31
+
32
+ comments_attributes = { "0" => { id: 1, name: '3' }, "1" => { name: '4' } }
31
33
 
34
+ binding.pry
@@ -1,15 +1,8 @@
1
1
  module SmoothOperator
2
-
3
- class ArrayWithMetaData < OpenStruct::Base
4
-
5
- extend Forwardable
6
-
7
- include Enumerable
2
+ class ArrayWithMetaData < ::SimpleDelegator
8
3
 
9
4
  attr_reader :meta_data, :internal_array
10
5
 
11
- def_delegators :internal_array, :length, :<<, :[]
12
-
13
6
  def initialize(attributes, object_class)
14
7
  _attributes, _resources_name = attributes.dup, object_class.resources_name
15
8
 
@@ -17,18 +10,14 @@ module SmoothOperator
17
10
  _attributes.delete(_resources_name)
18
11
 
19
12
  @meta_data = _attributes
20
- end
21
13
 
22
-
23
- def each
24
- internal_array.each { |array_entry| yield array_entry }
14
+ super(@internal_array)
25
15
  end
26
-
16
+
27
17
  def method_missing(method, *args, &block)
28
18
  _method = method.to_s
29
19
  meta_data.include?(_method) ? meta_data[_method] : super
30
20
  end
31
21
 
32
22
  end
33
-
34
23
  end
@@ -3,7 +3,6 @@ require 'smooth_operator/attributes/dirty'
3
3
  require 'smooth_operator/attributes/normal'
4
4
 
5
5
  module SmoothOperator
6
-
7
6
  module AttributeAssignment
8
7
 
9
8
  def self.included(base)
@@ -44,7 +43,6 @@ module SmoothOperator
44
43
 
45
44
  end
46
45
 
47
-
48
46
  def initialize(attributes = {}, options = {})
49
47
  @_options = {}
50
48
 
@@ -70,7 +68,7 @@ module SmoothOperator
70
68
 
71
69
  options.each { |key, value| @_options[key] = value } if options.is_a?(Hash)
72
70
 
73
- attributes.each { |name, value| push_to_internal_data(name, value, true) }
71
+ attributes.each { |name, value| push_to_internal_data(name, value) }
74
72
  end
75
73
 
76
74
  def parent_object
@@ -83,40 +81,6 @@ module SmoothOperator
83
81
 
84
82
  alias :from_server :has_data_from_server
85
83
 
86
- def internal_data
87
- @internal_data ||= {}
88
- end
89
-
90
- def get_internal_data(field, method = :value)
91
- result = internal_data[field]
92
-
93
- if result.nil?
94
- nil
95
- elsif method == :value
96
- result.is_a?(Attributes::Dirty) ? internal_data[field].send(method) : internal_data[field]
97
- else
98
- internal_data[field].send(method)
99
- end
100
- end
101
-
102
- def push_to_internal_data(attribute_name, attribute_value, cast = false)
103
- attribute_name = attribute_name.to_s
104
-
105
- return nil unless allowed_attribute(attribute_name)
106
-
107
- known_attributes.add attribute_name
108
-
109
- if internal_data[attribute_name].nil?
110
- initiate_internal_data(attribute_name, attribute_value, cast)
111
- else
112
- update_internal_data(attribute_name, attribute_value, cast)
113
- end
114
-
115
- if self.class.respond_to?(:smooth_operator?) && attribute_name == self.class.primary_key
116
- new_record?(true)
117
- end
118
- end
119
-
120
84
  protected #################### PROTECTED METHODS DOWN BELOW ######################
121
85
 
122
86
  def before_initialize(attributes, options); end
@@ -133,33 +97,5 @@ module SmoothOperator
133
97
  end
134
98
  end
135
99
 
136
-
137
- private ######################## PRIVATE #############################
138
-
139
- def initiate_internal_data(attribute_name, attribute_value, cast)
140
- if cast
141
- internal_data[attribute_name] = new_attribute_object(attribute_name, attribute_value)
142
-
143
- internal_data[attribute_name] = internal_data[attribute_name].value unless self.class.dirty_attributes?
144
- else
145
- internal_data[attribute_name] = attribute_value
146
- end
147
- end
148
-
149
- def update_internal_data(attribute_name, attribute_value, cast)
150
- if self.class.dirty_attributes?
151
- internal_data[attribute_name].set_value(attribute_value, self)
152
- else
153
- internal_data[attribute_name] = cast ? new_attribute_object(attribute_name, attribute_value).value : attribute_value
154
- end
155
- end
156
-
157
- def new_attribute_object(attribute_name, attribute_value)
158
- attribute_class = self.class.dirty_attributes? ? Attributes::Dirty : Attributes::Normal
159
-
160
- attribute_class.new(attribute_name, attribute_value, self)
161
- end
162
-
163
100
  end
164
-
165
101
  end
@@ -0,0 +1,78 @@
1
+ module SmoothOperator
2
+ module AttributeMethods
3
+
4
+ def internal_data
5
+ @internal_data ||= {}
6
+ end
7
+
8
+ def get_internal_data(field, method = :value)
9
+ result = internal_data[field]
10
+
11
+ if result.nil?
12
+ nil
13
+ elsif method == :value
14
+ result.is_a?(Attributes::Dirty) ? internal_data[field].send(method) : internal_data[field]
15
+ else
16
+ internal_data[field].send(method)
17
+ end
18
+ end
19
+
20
+ def get_attribute_type(attribute)
21
+ self.class.internal_structure[attribute.to_s]
22
+ end
23
+
24
+ def push_to_internal_data(attribute_name, attribute_value)
25
+ attribute_name = attribute_name.to_s
26
+
27
+ return nil unless allowed_attribute(attribute_name)
28
+
29
+ known_attributes.add attribute_name
30
+
31
+ initiate_or_update_internal_data(attribute_name, attribute_value)
32
+
33
+ new_record_or_mark_for_destruction?(attribute_name, attribute_value)
34
+ end
35
+
36
+ protected #################### PROTECTED METHODS DOWN BELOW ######################
37
+
38
+ def initiate_or_update_internal_data(attribute_name, attribute_value)
39
+ if internal_data[attribute_name].nil?
40
+ initiate_internal_data(attribute_name, attribute_value)
41
+ else
42
+ update_internal_data(attribute_name, attribute_value)
43
+ end
44
+ end
45
+
46
+ def new_record_or_mark_for_destruction?(attribute_name, attribute_value)
47
+ return nil unless self.class.respond_to?(:smooth_operator?)
48
+
49
+ marked_for_destruction?(attribute_value) if attribute_name == self.class.destroy_key
50
+
51
+ new_record?(true) if attribute_name == self.class.primary_key
52
+ end
53
+
54
+ private ######################## PRIVATE #############################
55
+
56
+ def initiate_internal_data(attribute_name, attribute_value)
57
+ internal_data[attribute_name] = new_attribute_object(attribute_name, attribute_value)
58
+
59
+ internal_data[attribute_name] = internal_data[attribute_name].value unless self.class.dirty_attributes?
60
+ end
61
+
62
+ def update_internal_data(attribute_name, attribute_value)
63
+ if self.class.dirty_attributes?
64
+ internal_data[attribute_name].set_value(attribute_value, self)
65
+ else
66
+ internal_data[attribute_name] = new_attribute_object(attribute_name, attribute_value).value
67
+ end
68
+ end
69
+
70
+ def new_attribute_object(attribute_name, attribute_value)
71
+ attribute_class = self.class.dirty_attributes? ? Attributes::Dirty : Attributes::Normal
72
+
73
+ attribute_class.new(attribute_name, attribute_value, self)
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -6,7 +6,7 @@ module SmoothOperator
6
6
  protected ##################### PROTECTED ########################
7
7
 
8
8
  def cast_to_type(name, value, parent_object)
9
- known_by_schema, type, unknown_hash_class = parent_object.internal_structure.include?(name), parent_object.internal_structure[name], parent_object.class.unknown_hash_class
9
+ known_by_schema, type, unknown_hash_class = parent_object.known_by_schema?(name), parent_object.get_attribute_type(name), parent_object.class.unknown_hash_class
10
10
 
11
11
  return Helpers.duplicate(value) if known_by_schema && type.nil?
12
12
 
@@ -25,7 +25,7 @@ module SmoothOperator
25
25
 
26
26
  when :string, :text, String
27
27
  value.to_s
28
-
28
+
29
29
  when :int, :integer, Integer, Fixnum
30
30
  to_int(value)
31
31
 
@@ -74,7 +74,7 @@ module SmoothOperator
74
74
  return string if string.is_a?(Float)
75
75
 
76
76
  return 0 if string.nil? || !(string.is_a?(String) || string.is_a?(Fixnum))
77
-
77
+
78
78
  value = string.to_s.gsub(',', '.').scan(/-*\d+[.]*\d*/).flatten.map(&:to_f).first
79
79
 
80
80
  value.nil? ? 0 : value
@@ -0,0 +1,7 @@
1
+ # THANKS tdantas! https://github.com/tdantas
2
+
3
+ module SmoothOperator
4
+ class BlankSlate
5
+ instance_methods.each { |m| undef_method m unless m =~ /^__|object_id/ }
6
+ end
7
+ end
@@ -2,8 +2,8 @@ module SmoothOperator
2
2
 
3
3
  module Delegation
4
4
 
5
- def respond_to?(method)
6
- known_attributes.include?(method.to_s) ? true : super
5
+ def respond_to?(method, include_private = false)
6
+ known_attribute?(method) ? true : super
7
7
  end
8
8
 
9
9
  def method_missing(method, *args, &block)
@@ -17,7 +17,9 @@ module SmoothOperator
17
17
  when :setter
18
18
  return push_to_internal_data(method_name, args.first)
19
19
  else
20
- return get_internal_data(method_name) if !self.class.strict_behaviour || respond_to?(method_name)
20
+ if !self.class.strict_behaviour || known_attribute?(method_name)
21
+ return get_internal_data(method_name)
22
+ end
21
23
  end
22
24
 
23
25
  result.nil? ? super : result
@@ -28,7 +30,7 @@ module SmoothOperator
28
30
 
29
31
  def parse_method(method)
30
32
  method = method.to_s
31
-
33
+
32
34
  if method?(method, /=$/)
33
35
  [:setter, method[0..-2]]
34
36
  elsif method?(method, /_was$/)
@@ -3,7 +3,15 @@ module SmoothOperator
3
3
  module Helpers
4
4
 
5
5
  extend self
6
-
6
+
7
+ def safe_call(object, method, *args)
8
+ if object.respond_to?(method)
9
+ object.send(method, *args)
10
+ else
11
+ false
12
+ end
13
+ end
14
+
7
15
  def super_method(object, method_name, *args)
8
16
  object.superclass.send(method_name, *args) if object.superclass.respond_to?(method_name)
9
17
  end
@@ -35,6 +43,11 @@ module SmoothOperator
35
43
  end
36
44
  end
37
45
 
46
+ def plural?(string)
47
+ string = string.to_s
48
+ string == string.pluralize
49
+ end
50
+
38
51
  def duplicate(object)
39
52
  object.dup rescue object
40
53
  end
@@ -61,7 +74,6 @@ module SmoothOperator
61
74
  def remove_initial_slash(string)
62
75
  string[1..-1]
63
76
  end
64
-
77
+
65
78
  end
66
-
67
79
  end
@@ -1,22 +1,24 @@
1
1
  module SmoothOperator
2
-
3
2
  module ModelSchema
4
3
 
5
4
  def self.included(base)
6
5
  base.extend(ClassMethods)
7
6
  end
8
7
 
8
+ def known_attribute?(attribute)
9
+ known_attributes.include?(attribute.to_s)
10
+ end
11
+
12
+ def known_by_schema?(attribute)
13
+ self.class.internal_structure.include?(attribute.to_s)
14
+ end
15
+
9
16
  def known_attributes
10
17
  @known_attributes ||= self.class.known_attributes.dup
11
18
  end
12
19
 
13
- def internal_structure
14
- @internal_structure ||= self.class.internal_structure.dup
15
- end
16
-
17
-
18
20
  module ClassMethods
19
-
21
+
20
22
  def resources_name(default_bypass = nil)
21
23
  return @resources_name if defined?(@resources_name)
22
24
 
@@ -58,7 +60,6 @@ module SmoothOperator
58
60
  def model_name=(name)
59
61
  @_model_name = name
60
62
  end
61
-
62
63
 
63
64
  protected ############## PROTECTED #############
64
65
 
@@ -77,5 +78,4 @@ module SmoothOperator
77
78
  end
78
79
 
79
80
  end
80
-
81
81
  end
@@ -2,6 +2,7 @@ require "smooth_operator/delegation"
2
2
  require "smooth_operator/validations"
3
3
  require "smooth_operator/model_schema"
4
4
  require "smooth_operator/serialization"
5
+ require "smooth_operator/attribute_methods"
5
6
  require "smooth_operator/attribute_assignment"
6
7
 
7
8
  module SmoothOperator
@@ -13,6 +14,7 @@ module SmoothOperator
13
14
  include Validations
14
15
  include ModelSchema
15
16
  include Serialization
17
+ include AttributeMethods
16
18
  include AttributeAssignment
17
19
 
18
20
  def self.strict_behaviour=(value)
@@ -28,7 +30,7 @@ module SmoothOperator
28
30
  class Dirty < Base
29
31
 
30
32
  dirty_attributes
31
-
33
+
32
34
  end
33
35
 
34
36
  end
@@ -5,18 +5,17 @@ require "smooth_operator/remote_call/errors/timeout"
5
5
  require "smooth_operator/remote_call/errors/connection_failed"
6
6
 
7
7
  module SmoothOperator
8
-
9
8
  module Operator
10
9
 
11
10
  def make_the_call(http_verb, relative_path = '', data = {}, options = {})
12
11
  options ||= {}
13
-
12
+
14
13
  relative_path = resource_path(relative_path)
15
-
14
+
16
15
  if !parent_object.nil? && options[:ignore_parent] != true
17
16
  options[:resources_name] ||= "#{parent_object.class.resources_name}/#{parent_object.get_primary_key}/#{self.class.resources_name}"
18
17
  end
19
-
18
+
20
19
  self.class.make_the_call(http_verb, relative_path, data, options) do |remote_call|
21
20
  yield(remote_call)
22
21
  end
@@ -34,13 +33,12 @@ module SmoothOperator
34
33
  end
35
34
  end
36
35
 
37
-
38
36
  ########################### MODULES BELLOW ###############################
39
37
 
40
38
  module HttpMethods
41
39
 
42
40
  HTTP_VERBS = %w[get post put patch delete]
43
-
41
+
44
42
  HTTP_VERBS.each do |method|
45
43
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
46
44
  def #{method}(relative_path = '', params = {}, options = {})
@@ -78,7 +76,7 @@ module SmoothOperator
78
76
  else
79
77
  operator_call = Operators::Faraday
80
78
  end
81
-
79
+
82
80
  operator_call.make_the_call(*operator_args) do |remote_call|
83
81
  block_given? ? yield(remote_call) : remote_call
84
82
  end
@@ -88,7 +86,6 @@ module SmoothOperator
88
86
  params
89
87
  end
90
88
 
91
-
92
89
  protected #################### PROTECTED ##################
93
90
 
94
91
  def operator_method_args(http_verb, relative_path, data, options)
@@ -97,7 +94,6 @@ module SmoothOperator
97
94
  [http_verb, resource_path(relative_path, options), *strip_params(http_verb, data), options]
98
95
  end
99
96
 
100
-
101
97
  private #################### PRIVATE ##################
102
98
 
103
99
  def populate_options(options)
@@ -106,7 +102,7 @@ module SmoothOperator
106
102
  OPTIONS.each { |option| options[option] ||= send(option) }
107
103
 
108
104
  options[:headers] = headers.merge(options[:headers] || {})
109
-
105
+
110
106
  options
111
107
  end
112
108
 
@@ -122,17 +118,15 @@ module SmoothOperator
122
118
 
123
119
  def strip_params(http_verb, data)
124
120
  data ||= {}
125
-
121
+
126
122
  if [:get, :head, :delete].include?(http_verb)
127
123
  [query_string(data), nil]
128
124
  else
129
125
  [query_string({}), data]
130
126
  end
131
127
  end
132
-
133
128
  end
134
129
 
135
-
136
130
  include HttpMethods
137
131
 
138
132
  def self.included(base)
@@ -1,5 +1,4 @@
1
1
  module SmoothOperator
2
-
3
2
  module Persistence
4
3
 
5
4
  def self.included(base)
@@ -8,7 +7,6 @@ module SmoothOperator
8
7
 
9
8
  attr_reader :last_remote_call
10
9
 
11
-
12
10
  def get_primary_key
13
11
  get_internal_data(self.class.primary_key)
14
12
  end
@@ -27,6 +25,12 @@ module SmoothOperator
27
25
  @new_record = Helpers.blank?(get_primary_key)
28
26
  end
29
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
+
30
34
  def destroyed?
31
35
  return @destroyed if defined?(@destroyed)
32
36
 
@@ -105,32 +109,36 @@ module SmoothOperator
105
109
 
106
110
  { self.class.resource_name => hash }.merge(data)
107
111
  end
108
-
109
-
112
+
113
+
110
114
  module ClassMethods
111
-
115
+
112
116
  METHODS_VS_HTTP_VERBS = { reload: :get, create: :post, update: :put, destroy: :delete }
113
-
117
+
114
118
  def methods_vs_http_verbs
115
119
  Helpers.get_instance_variable(self, :methods_vs_http_verbs, METHODS_VS_HTTP_VERBS.dup)
116
120
  end
117
-
121
+
118
122
  def primary_key
119
123
  Helpers.get_instance_variable(self, :primary_key, 'id')
120
124
  end
121
125
 
122
126
  attr_writer :primary_key
123
127
 
128
+ def destroy_key
129
+ Helpers.get_instance_variable(self, :destroy_key, '_destroy')
130
+ end
131
+
132
+ attr_writer :destroy_key
133
+
124
134
  METHODS_VS_HTTP_VERBS.keys.each do |method|
125
135
  define_method("#{method}_http_verb=") { |http_verb| methods_vs_http_verbs[method] = http_verb }
126
136
  end
127
-
137
+
128
138
  def create(attributes = nil, relative_path = nil, data = {}, options = {})
129
139
  new(attributes).tap { |object| object.save(relative_path, data, options) }
130
140
  end
131
-
132
- end
133
141
 
142
+ end
134
143
  end
135
-
136
144
  end
@@ -0,0 +1,55 @@
1
+ module SmoothOperator
2
+ module Relation
3
+ class ArrayRelation < ::SimpleDelegator
4
+
5
+ attr_reader :object, :association
6
+
7
+ def initialize(object, association)
8
+ @object, @association = object, association
9
+ end
10
+
11
+ def reload
12
+ "TODO"
13
+ end
14
+
15
+ def new(attributes = {})
16
+ object.class.reflect_on_association(association).klass.new(attributes)
17
+ end
18
+
19
+ def build(attributes = {})
20
+ new_array, new_array_entry = get_array, new(attributes)
21
+
22
+ new_array.push new_array_entry
23
+
24
+ object.send("#{association}=", new_array)
25
+
26
+ new_array_entry
27
+ end
28
+
29
+ undef :is_a?
30
+
31
+ protected ############### PROTECTED ###############
32
+
33
+ # def method_missing(method, *args, &block)
34
+ # if get_array.respond_to?(method)
35
+ # puts "if true #{method} - #{args}"
36
+ # get_array.send(method, *args)
37
+ # else
38
+ # puts "if else #{method}"
39
+ # super
40
+ # end
41
+ # end
42
+
43
+ def get_array
44
+ data = object.get_internal_data(association.to_s)
45
+
46
+ data.nil? ? [] : [*data]
47
+ end
48
+
49
+ def refresh
50
+ __setobj__ get_array
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,79 @@
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
+ def collection?
60
+ has_many?
61
+ end
62
+
63
+ private ################################# private
64
+
65
+ def macro_default(association)
66
+ Helpers.plural?(association) ? :has_many : :belongs_to
67
+ end
68
+
69
+ def foreign_key_default
70
+ if has_many? || has_one?
71
+ "#{related_reflection.single_name}_id"
72
+ elsif belongs_to?
73
+ "#{single_name}_id"
74
+ end.to_sym
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,102 @@
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 has_many(nested_object_name, options = {})
9
+ accepts_nested_objects(nested_object_name, :has_many, options)
10
+ end
11
+
12
+ def has_one(nested_object_name, options = {})
13
+ accepts_nested_objects(nested_object_name, :has_one, options)
14
+ end
15
+
16
+ def belongs_to(nested_object_name, options = {})
17
+ accepts_nested_objects(nested_object_name, :belongs_to, options)
18
+ end
19
+
20
+ def reflections
21
+ Helpers.get_instance_variable(self, :reflections, {})
22
+ end
23
+
24
+ def reflect_on_association(association)
25
+ reflections[association]
26
+ end
27
+
28
+ def reflect_on_all_associations(macro = nil)
29
+ macro ? reflections.values.select { |reflection| reflection.macro == macro } : reflections.values
30
+ end
31
+
32
+ protected ###################### PROTECTED ###################
33
+
34
+ def accepts_nested_objects(association, macro, options = {})
35
+ options = parse_options(options, { macro: macro })
36
+
37
+ reflection = AssociationReflection.new(association, Reflection.new(name, {}), options)
38
+
39
+ schema(association => reflection.klass)
40
+
41
+ reflections.merge!(association => reflection)
42
+
43
+ if reflection.has_many?
44
+ define_has_many_association_method(reflection, association)
45
+ else
46
+ define_single_association_method(reflection, association)
47
+ end
48
+
49
+ self.send(:attr_reader, "#{association}_attributes".to_sym)
50
+
51
+ define_attributes_setter_methods(reflection, association)
52
+ end
53
+
54
+ private ####################### PRIVATE ######################
55
+
56
+ def define_has_many_association_method(reflection, association)
57
+ define_method(association) do
58
+ array_relation = instance_variable_get("@#{association}")
59
+
60
+ if array_relation.nil?
61
+ array_relation = ArrayRelation.new(self, association)
62
+
63
+ instance_variable_set("@#{association}", array_relation)
64
+ end
65
+
66
+ array_relation.send(:refresh)
67
+
68
+ array_relation
69
+ end
70
+ end
71
+
72
+ def define_single_association_method(reflection, association)
73
+ define_method(association) { get_internal_data(association.to_s) }
74
+
75
+ define_method("build_#{association}") do |attributes = {}|
76
+ new_instance = reflection.klass.new(attributes)
77
+
78
+ push_to_internal_data(association, new_instance)
79
+
80
+ new_instance
81
+ end
82
+ end
83
+
84
+ def define_attributes_setter_methods(reflection, association)
85
+ define_method("#{association}_attributes=") do |attributes|
86
+ instance_variable_set("@#{association}_attributes", attributes)
87
+
88
+ attributes = attributes.values if reflection.has_many?
89
+
90
+ push_to_internal_data(association.to_s, attributes)
91
+ end
92
+ end
93
+
94
+ def parse_options(options, default_options)
95
+ options = options.is_a?(Hash) ? options.merge(default_options) : default_options
96
+
97
+ Helpers.symbolyze_keys(options)
98
+ end
99
+
100
+ end
101
+ end
102
+ 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
@@ -14,7 +14,6 @@ module SmoothOperator
14
14
  @response = response
15
15
  end
16
16
 
17
-
18
17
  def ok?
19
18
  http_status.between?(200, 299) || http_status == 304
20
19
  end
@@ -35,7 +34,6 @@ module SmoothOperator
35
34
  http_status.between?(500, 599) || http_status == 0
36
35
  end
37
36
 
38
-
39
37
  def not_found?
40
38
  http_status == 404
41
39
  end
@@ -48,12 +46,11 @@ module SmoothOperator
48
46
  false
49
47
  end
50
48
 
51
-
52
49
  def parsed_response
53
50
  return nil if body.nil?
54
-
51
+
55
52
  require 'json' unless defined? JSON
56
-
53
+
57
54
  begin
58
55
  JSON.parse(body)
59
56
  rescue JSON::ParserError
@@ -74,7 +71,5 @@ module SmoothOperator
74
71
  end
75
72
 
76
73
  end
77
-
78
74
  end
79
-
80
75
  end
@@ -1,27 +1,24 @@
1
1
  module SmoothOperator
2
-
3
2
  module Translation
4
3
 
5
4
  def human_attribute_name(attribute_key_name, options = {})
6
5
  _translate("attributes.#{model_name.i18n_key}.#{attribute_key_name}", options = {})
7
6
  end
8
7
 
9
-
10
8
  private ###################### PRIVATE #########################
11
9
 
12
10
  def _translate(namespace = '', options = {})
13
11
  no_translation = "-- no translation --"
14
-
12
+
15
13
  defaults = ["smooth_operator.#{namespace}".to_sym]
16
14
  defaults << "activerecord.#{namespace}".to_sym
17
15
  defaults << options[:default] if options[:default]
18
16
  defaults.flatten!
19
17
  defaults << no_translation
20
-
18
+
21
19
  options = { count: 1, default: defaults }.merge!(options.except(:default))
22
20
  I18n.translate(defaults.shift, options)
23
21
  end
24
22
 
25
23
  end
26
-
27
24
  end
@@ -1,3 +1,3 @@
1
1
  module SmoothOperator
2
- VERSION = "1.11.2"
2
+ VERSION = "1.20.9"
3
3
  end
@@ -5,11 +5,13 @@ require "smooth_operator/persistence"
5
5
  require "smooth_operator/translation"
6
6
  require "smooth_operator/open_struct"
7
7
  require "smooth_operator/finder_methods"
8
+ require "smooth_operator/relation/associations"
8
9
 
9
10
  module SmoothOperator
10
11
  class Base < OpenStruct::Base
11
12
 
12
13
  extend FinderMethods
14
+ extend Relation::Associations
13
15
  extend Translation if defined? I18n
14
16
 
15
17
  include Operator
@@ -23,4 +25,20 @@ module SmoothOperator
23
25
  end
24
26
 
25
27
  end
28
+
29
+ if defined?(ActiveModel)
30
+ class Rails < Base
31
+
32
+ include ActiveModel::Validations
33
+ include ActiveModel::Validations::Callbacks
34
+ include ActiveModel::Conversion
35
+
36
+ def column_for_attribute(attribute_name)
37
+ type = get_attribute_type(attribute_name)
38
+
39
+ ActiveRecord::ConnectionAdapters::Column.new(attribute_name.to_sym, type, type)
40
+ end
41
+
42
+ end
43
+ end
26
44
  end
@@ -20,12 +20,12 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
22
  spec.require_paths = ["lib"]
23
-
23
+
24
24
  spec.add_development_dependency "bundler", "~> 1.3"
25
-
26
- spec.add_dependency "json"
27
- spec.add_dependency "faraday", "~> 0.8.9"
28
- spec.add_dependency "typhoeus", "~> 0.6.8"
25
+
26
+ spec.add_dependency "json", '~> 1.8'
27
+ spec.add_dependency "faraday", "~> 0.8"
28
+ spec.add_dependency "typhoeus", "~> 0.6"
29
29
 
30
30
  # this is necessary if you want to typhoeus to correctly encode arrays
31
31
  # spec.add_dependency "ethon", :git => 'https://github.com/goncalvesjoao/ethon'
@@ -4,7 +4,7 @@ require "spec_helper"
4
4
  describe SmoothOperator::AttributeAssignment do
5
5
 
6
6
  describe "#assign_attributes" do
7
-
7
+
8
8
  describe "receiving data from server" do
9
9
  subject { User::Base.new }
10
10
 
@@ -70,7 +70,7 @@ describe SmoothOperator::AttributeAssignment do
70
70
  end
71
71
 
72
72
  context "when one of the attribute's value, is an hash and is unknown to the schema" do
73
- context "when the .unknown_hash_class is unused" do
73
+ context "when the .unknown_hash_class is unused", current: true do
74
74
  subject { User::Base.new(address: { street: 'something', postal_code: { code: '123' } }) }
75
75
 
76
76
  it "a new instance of OpenStruct will be initialized with that hash" do
@@ -83,7 +83,7 @@ describe SmoothOperator::AttributeAssignment do
83
83
  expect(address.postal_code.code).to eq('123')
84
84
  end
85
85
  end
86
-
86
+
87
87
  context "when the .unknown_hash_class is set to SmoothOperator::OpenStruct::Base" do
88
88
  subject { User::UnknownHashClass::OpenStructBase.new(address: { street: 'something', postal_code: { code: '123' } }) }
89
89
 
@@ -246,7 +246,7 @@ describe SmoothOperator::AttributeAssignment do
246
246
  it "when the attributes's value is 'false', should be converted to false" do
247
247
  expect(subject.new(manager: 'false').manager).to be(false)
248
248
  end
249
-
249
+
250
250
  it "when the attributes's value is '1', should be converted to true" do
251
251
  expect(subject.new(manager: '1').manager).to be(true)
252
252
  end
@@ -264,7 +264,7 @@ describe SmoothOperator::AttributeAssignment do
264
264
  end
265
265
 
266
266
  end
267
-
267
+
268
268
  context "that is declared (in schema) as an existing class" do
269
269
 
270
270
  it "if the attribute's value is an hash a new instance of that class will be initialized with that hash" do
@@ -280,12 +280,12 @@ describe SmoothOperator::AttributeAssignment do
280
280
 
281
281
  it "if the attribute's value is an array, a new instance of that class will be initialized for each array entry" do
282
282
  posts = subject.new(posts: [{ body: 'post1' }, { body: 'post2' }]).posts
283
-
283
+
284
284
  expect(posts.length).to be(2)
285
285
 
286
286
  expect(posts[0]).to be_instance_of(Post)
287
287
  expect(posts[0].body).to eq('post1')
288
-
288
+
289
289
  expect(posts[1]).to be_instance_of(Post)
290
290
  expect(posts[1].body).to eq('post2')
291
291
  end
@@ -10,5 +10,5 @@ class Address < SmoothOperator::Base
10
10
  self.endpoint = 'http://localhost:4567/'
11
11
 
12
12
  self.headers = { "X-APPTOKEN" => "joaquim_app_token", "X-LAYERTOKEN" => "joaquim_layer_token" }
13
-
13
+
14
14
  end
@@ -0,0 +1,3 @@
1
+ class Comment < SmoothOperator::Base
2
+
3
+ end
@@ -3,7 +3,11 @@ class Post < SmoothOperator::Base
3
3
  self.endpoint_user = 'admin'
4
4
 
5
5
  self.endpoint_pass = 'admin'
6
-
6
+
7
7
  self.endpoint = 'http://localhost:4567/'
8
8
 
9
+ has_many :comments
10
+
11
+ belongs_to :address
12
+
9
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smooth_operator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.2
4
+ version: 1.20.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Gonçalves
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-28 00:00:00.000000000 Z
11
+ date: 2014-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,44 +28,44 @@ dependencies:
28
28
  name: json
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.8'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '1.8'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: faraday
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: 0.8.9
47
+ version: '0.8'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: 0.8.9
54
+ version: '0.8'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: typhoeus
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 0.6.8
61
+ version: '0.6'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ~>
67
67
  - !ruby/object:Gem::Version
68
- version: 0.6.8
68
+ version: '0.6'
69
69
  description: ActiveResource alternative
70
70
  email:
71
71
  - goncalves.joao@gmail.com
@@ -83,9 +83,11 @@ files:
83
83
  - lib/smooth_operator.rb
84
84
  - lib/smooth_operator/array_with_meta_data.rb
85
85
  - lib/smooth_operator/attribute_assignment.rb
86
+ - lib/smooth_operator/attribute_methods.rb
86
87
  - lib/smooth_operator/attributes/base.rb
87
88
  - lib/smooth_operator/attributes/dirty.rb
88
89
  - lib/smooth_operator/attributes/normal.rb
90
+ - lib/smooth_operator/blank_slate.rb
89
91
  - lib/smooth_operator/delegation.rb
90
92
  - lib/smooth_operator/finder_methods.rb
91
93
  - lib/smooth_operator/helpers.rb
@@ -95,6 +97,10 @@ files:
95
97
  - lib/smooth_operator/operators/faraday.rb
96
98
  - lib/smooth_operator/operators/typhoeus.rb
97
99
  - lib/smooth_operator/persistence.rb
100
+ - lib/smooth_operator/relation/array_relation.rb
101
+ - lib/smooth_operator/relation/association_reflection.rb
102
+ - lib/smooth_operator/relation/associations.rb
103
+ - lib/smooth_operator/relation/reflection.rb
98
104
  - lib/smooth_operator/remote_call/base.rb
99
105
  - lib/smooth_operator/remote_call/errors/connection_failed.rb
100
106
  - lib/smooth_operator/remote_call/errors/timeout.rb
@@ -121,6 +127,7 @@ files:
121
127
  - spec/support/helpers/persistence_helper.rb
122
128
  - spec/support/localhost_server.rb
123
129
  - spec/support/models/address.rb
130
+ - spec/support/models/comment.rb
124
131
  - spec/support/models/post.rb
125
132
  - spec/support/models/user.rb
126
133
  - spec/support/models/user_with_address_and_posts.rb
@@ -145,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
152
  version: '0'
146
153
  requirements: []
147
154
  rubyforge_project:
148
- rubygems_version: 2.1.10
155
+ rubygems_version: 2.2.2
149
156
  signing_key:
150
157
  specification_version: 4
151
158
  summary: Simple and fully customizable alternative to ActiveResource, using faraday
@@ -167,6 +174,7 @@ test_files:
167
174
  - spec/support/helpers/persistence_helper.rb
168
175
  - spec/support/localhost_server.rb
169
176
  - spec/support/models/address.rb
177
+ - spec/support/models/comment.rb
170
178
  - spec/support/models/post.rb
171
179
  - spec/support/models/user.rb
172
180
  - spec/support/models/user_with_address_and_posts.rb