her 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b11481769c945a97654d3d95432044346945d41
4
- data.tar.gz: 5d9d9d9f02a10ccd3424314ed13b870651995637
3
+ metadata.gz: b1d26b46a64420261f94f9394f9552d0f6152d27
4
+ data.tar.gz: '009e2b3a0c746e2ff24d2c96e7b3e110852b6e5c'
5
5
  SHA512:
6
- metadata.gz: 0ba766a53928bf5a48027e5caaee70081768a8c924c1a306afca64dd6e72ded1dc5999da454f3cdd66ca47c96ad41e639ae4bd8bed8df8bd33a269d7d28584cc
7
- data.tar.gz: cf562af326ca2bc7d77be3f27de84cfd8d17620775833947815ff985751c11fa89153c4bb4de78027f7bbd36d55209b4cd2f36095e003c250b0ccd9ae5b14d1b
6
+ metadata.gz: 119265f2cfea3d575768faf7f5b9dbec5e88da8536068dd51957251cdf921b275e4527767ca57f647172951341bfcca6a3d0363ff0091cbb2f40b0ec89c922fc
7
+ data.tar.gz: d74157011df12022512bc46df0ae02cc48f69bd89753a72eac0e32b88921d9356e2073c8b63e238d925304f3bf4ba07418d7c58898bf87308e8deebbacc46b48
@@ -3,9 +3,9 @@ language: ruby
3
3
  sudo: false
4
4
 
5
5
  rvm:
6
- - 2.4.1
7
- - 2.3.1
8
- - 2.2.2
6
+ - 2.4.2
7
+ - 2.3.5
8
+ - 2.2.8
9
9
  - 2.1.6
10
10
  - 2.0.0
11
11
  - 1.9.3
data/README.md CHANGED
@@ -1,9 +1,3 @@
1
- # Maintenance Update 29th Sept 2016
2
-
3
- Hi folks, [@edtjones](https://github.com/edtjones) here. Rémi has handed me the keys to Her and [@foxpaul](https://github.com/foxpaul) and I will be trying to do the library justice with the help of the community. There's loads to do; we'll get in touch with everyone who's raised a PR as soon as possible and figure out a plan of action.
4
-
5
- # Rails 5 support
6
- If you need Rails 5 support, version 0.8.2 is for you!
7
1
 
8
2
  <p align="center">
9
3
  <a href="https://github.com/remiprev/her">
@@ -996,6 +990,7 @@ See the [UPGRADE.md](https://github.com/remiprev/her/blob/master/UPGRADE.md) for
996
990
  Most projects I know that use Her are internal or private projects but here’s a list of public ones:
997
991
 
998
992
  * [tumbz](https://github.com/remiprev/tumbz)
993
+ * [zoho-ruby](https://github.com/errorstudio/zoho-ruby)
999
994
  * [crowdher](https://github.com/simonprev/crowdher)
1000
995
  * [vodka](https://github.com/magnolia-fan/vodka)
1001
996
  * [webistrano_cli](https://github.com/chytreg/webistrano_cli)
@@ -1007,6 +1002,9 @@ I told myself a few months ago that it would be great to build a gem to replace
1007
1002
 
1008
1003
  Most of Her’s core concepts were written on a Saturday morning of April 2012 ([first commit](https://github.com/remiprev/her/commit/689d8e88916dc2ad258e69a2a91a283f061cbef2) at 7am!).
1009
1004
 
1005
+ ## Maintainers
1006
+ The gem is currently maintained by [@zacharywelch](https://github.com/zacharywelch) and [@edtjones](https://github.com/edtjones).
1007
+
1010
1008
  ## Contribute
1011
1009
 
1012
1010
  Yes please! Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues). There’s no such thing as a bad pull request — even if it’s for a typo, a small improvement to the code or the documentation!
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
21
21
  s.add_development_dependency "rspec", "~> 3.5"
22
22
  s.add_development_dependency "json", "~> 1.8"
23
23
 
24
- s.add_runtime_dependency "activemodel", ">= 3.0.0", "<= 6.0.0"
25
- s.add_runtime_dependency "activesupport", ">= 3.0.0", "<= 6.0.0"
24
+ s.add_runtime_dependency "activemodel", ">= 3.0.0", "< 5.2.0"
25
+ s.add_runtime_dependency "activesupport", ">= 3.0.0", "< 5.2.0"
26
26
  s.add_runtime_dependency "faraday", ">= 0.8", "< 1.0"
27
27
  s.add_runtime_dependency "multi_json", "~> 1.7"
28
28
  end
@@ -85,7 +85,7 @@ module Her
85
85
  def fetch
86
86
  super.tap do |o|
87
87
  inverse_of = @opts[:inverse_of] || @parent.singularized_resource_name
88
- o.each { |entry| entry.send("#{inverse_of}=", @parent) }
88
+ o.each { |entry| entry.attributes[inverse_of] = @parent }
89
89
  end
90
90
  end
91
91
 
@@ -39,7 +39,7 @@ module Her
39
39
  #
40
40
  # @private
41
41
  def method_missing(method, *args, &blk)
42
- if method.to_s =~ /[?=]$/ || @attributes.include?(method)
42
+ if method.to_s =~ /[?=]$/ || @_her_attributes.include?(method)
43
43
  # Extract the attribute
44
44
  attribute = method.to_s.sub(/[?=]$/, '')
45
45
 
@@ -55,7 +55,7 @@ module Her
55
55
 
56
56
  # @private
57
57
  def respond_to_missing?(method, include_private = false)
58
- method.to_s =~ /[?=]$/ || @attributes.include?(method) || super
58
+ method.to_s =~ /[?=]$/ || @_her_attributes.include?(method) || super
59
59
  end
60
60
 
61
61
  # Assign new attributes to a resource
@@ -69,40 +69,47 @@ module Her
69
69
  # user.assign_attributes(name: "Lindsay")
70
70
  # user.changes # => { :name => ["Tobias", "Lindsay"] }
71
71
  def assign_attributes(new_attributes)
72
- @attributes ||= attributes
72
+ @_her_attributes ||= attributes
73
73
  # Use setter methods first
74
74
  unset_attributes = self.class.use_setter_methods(self, new_attributes)
75
75
 
76
76
  # Then translate attributes of associations into association instances
77
- parsed_attributes = self.class.parse_associations(unset_attributes)
77
+ associations = self.class.parse_associations(unset_attributes)
78
78
 
79
- # Then merge the parsed_data into @attributes.
80
- @attributes.merge!(parsed_attributes)
79
+ # Then merge the associations into @_her_attributes.
80
+ @_her_attributes.merge!(associations)
81
81
  end
82
82
  alias attributes= assign_attributes
83
83
 
84
84
  def attributes
85
- @attributes ||= HashWithIndifferentAccess.new
85
+ # The natural choice of instance variable naming here would be
86
+ # `@attributes`. Unfortunately that causes a naming clash when
87
+ # used with `ActiveModel` version >= 5.2.0.
88
+ # As of v5.2.0 `ActiveModel` checks to see if `ActiveRecord`
89
+ # attributes exist, and assumes that if the instance variable
90
+ # `@attributes` exists on the instance, it is because they are
91
+ # `ActiveRecord` attributes.
92
+ @_her_attributes ||= HashWithIndifferentAccess.new
86
93
  end
87
94
 
88
95
  # Handles returning true for the accessible attributes
89
96
  #
90
97
  # @private
91
98
  def has_attribute?(attribute_name)
92
- @attributes.include?(attribute_name)
99
+ @_her_attributes.include?(attribute_name)
93
100
  end
94
101
 
95
102
  # Handles returning data for a specific attribute
96
103
  #
97
104
  # @private
98
105
  def get_attribute(attribute_name)
99
- @attributes[attribute_name]
106
+ @_her_attributes[attribute_name]
100
107
  end
101
108
  alias attribute get_attribute
102
109
 
103
110
  # Return the value of the model `primary_key` attribute
104
111
  def id
105
- @attributes[self.class.primary_key]
112
+ @_her_attributes[self.class.primary_key]
106
113
  end
107
114
 
108
115
  # Return `true` if the other object is also a Her::Model and has matching
@@ -110,7 +117,7 @@ module Her
110
117
  #
111
118
  # @private
112
119
  def ==(other)
113
- other.is_a?(Her::Model) && @attributes == other.attributes
120
+ other.is_a?(Her::Model) && @_her_attributes == other.attributes
114
121
  end
115
122
 
116
123
  # Delegate to the == method
@@ -120,27 +127,27 @@ module Her
120
127
  self == other
121
128
  end
122
129
 
123
- # Delegate to @attributes, allowing models to act correctly in code like:
130
+ # Delegate to @_her_attributes, allowing models to act correctly in code like:
124
131
  # [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
125
132
  # @private
126
133
  def hash
127
- @attributes.hash
134
+ @_her_attributes.hash
128
135
  end
129
136
 
130
137
  # Assign attribute value (ActiveModel convention method).
131
138
  #
132
139
  # @private
133
140
  def attribute=(attribute, value)
134
- @attributes[attribute] = nil unless @attributes.include?(attribute)
135
- self.send(:"#{attribute}_will_change!") if @attributes[attribute] != value
136
- @attributes[attribute] = value
141
+ @_her_attributes[attribute] = nil unless @_her_attributes.include?(attribute)
142
+ send("#{attribute}_will_change!") unless value == @_her_attributes[attribute]
143
+ @_her_attributes[attribute] = value
137
144
  end
138
145
 
139
146
  # Check attribute value to be present (ActiveModel convention method).
140
147
  #
141
148
  # @private
142
149
  def attribute?(attribute)
143
- @attributes.include?(attribute) && @attributes[attribute].present?
150
+ @_her_attributes.include?(attribute) && @_her_attributes[attribute].present?
144
151
  end
145
152
 
146
153
  module ClassMethods
@@ -155,6 +162,7 @@ module Her
155
162
  attributes = klass.parse(record).merge(_metadata: parsed_data[:metadata],
156
163
  _errors: parsed_data[:errors])
157
164
  klass.new(attributes).tap do |record|
165
+ record.instance_variable_set(:@changed_attributes, {})
158
166
  record.run_callbacks :find
159
167
  end
160
168
  end
@@ -164,10 +172,10 @@ module Her
164
172
  #
165
173
  # @private
166
174
  def instantiate_collection(klass, parsed_data = {})
167
- items = klass.extract_array(parsed_data).map do |item|
168
- instantiate_record(klass, data: item)
175
+ records = klass.extract_array(parsed_data).map do |record|
176
+ instantiate_record(klass, data: record)
169
177
  end
170
- Her::Collection.new(items, parsed_data[:metadata], parsed_data[:errors])
178
+ Her::Collection.new(records, parsed_data[:metadata], parsed_data[:errors])
171
179
  end
172
180
 
173
181
  # Initialize a collection of resources with raw data from an HTTP request
@@ -196,9 +204,9 @@ module Her
196
204
 
197
205
  setter_method_names = model.class.setter_method_names
198
206
  params.each_with_object({}) do |(key, value), memo|
199
- setter_method = key.to_s + '='
207
+ setter_method = "#{key}="
200
208
  if setter_method_names.include?(setter_method)
201
- model.send(setter_method, value)
209
+ model.send setter_method, value
202
210
  else
203
211
  memo[key.to_sym] = value
204
212
  end
@@ -38,7 +38,7 @@ module Her
38
38
  #
39
39
  # @private
40
40
  def her_nearby_class(name)
41
- her_sibling_class(name) || name.constantize rescue nil
41
+ her_sibling_class(name) || name.constantize
42
42
  end
43
43
 
44
44
  protected
@@ -41,12 +41,8 @@ module Her
41
41
 
42
42
  run_callbacks :save do
43
43
  run_callbacks callback do
44
- params = to_params
45
44
  self.class.request(to_params.merge(:_method => method, :_path => request_path)) do |parsed_data, response|
46
- assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
47
- @metadata = parsed_data[:metadata]
48
- @response_errors = parsed_data[:errors]
49
-
45
+ load_from_parsed_data(parsed_data)
50
46
  return false if !response.success? || @response_errors.any?
51
47
  if self.changed_attributes.present?
52
48
  @previously_changed = self.changes.clone
@@ -77,53 +73,13 @@ module Her
77
73
  method = self.class.method_for(:destroy)
78
74
  run_callbacks :destroy do
79
75
  self.class.request(params.merge(:_method => method, :_path => request_path)) do |parsed_data, response|
80
- assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
81
- @metadata = parsed_data[:metadata]
82
- @response_errors = parsed_data[:errors]
76
+ load_from_parsed_data(parsed_data)
83
77
  @destroyed = response.success?
84
78
  end
85
79
  end
86
80
  self
87
81
  end
88
82
 
89
- # Refetches the resource
90
- #
91
- # This method finds the resource by its primary key (which could be
92
- # assigned manually) and modifies the object in-place.
93
- #
94
- # @example
95
- # user = User.find(1)
96
- # # => #<User(users/1) id=1 name="Tobias Fünke">
97
- # user.name = "Oops"
98
- # user.reload # Fetched again via GET "/users/1"
99
- # # => #<User(users/1) id=1 name="Tobias Fünke">
100
- def reload(options = nil)
101
- fresh_object = self.class.find(id)
102
- assign_attributes(fresh_object.attributes)
103
- self
104
- end
105
-
106
- # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
107
- # if the predicate returns +true+ the attribute will become +false+. This
108
- # method toggles directly the underlying value without calling any setter.
109
- # Returns +self+.
110
- #
111
- # @example
112
- # user = User.first
113
- # user.admin? # => false
114
- # user.toggle(:admin)
115
- # user.admin? # => true
116
- def toggle(attribute)
117
- attributes[attribute] = !public_send("#{attribute}?")
118
- self
119
- end
120
-
121
- # Wrapper around #toggle that saves the resource. Saving is subjected to
122
- # validation checks. Returns +true+ if the record could be saved.
123
- def toggle!(attribute)
124
- toggle(attribute) && save
125
- end
126
-
127
83
  # Initializes +attribute+ to zero if +nil+ and adds the value passed as
128
84
  # +by+ (default is 1). The increment is performed directly on the
129
85
  # underlying attribute, no setter is invoked. Only makes sense for
@@ -155,6 +111,54 @@ module Her
155
111
  increment!(attribute, -by)
156
112
  end
157
113
 
114
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
115
+ # if the predicate returns +true+ the attribute will become +false+. This
116
+ # method toggles directly the underlying value without calling any setter.
117
+ # Returns +self+.
118
+ #
119
+ # @example
120
+ # user = User.first
121
+ # user.admin? # => false
122
+ # user.toggle(:admin)
123
+ # user.admin? # => true
124
+ def toggle(attribute)
125
+ attributes[attribute] = !public_send("#{attribute}?")
126
+ self
127
+ end
128
+
129
+ # Wrapper around #toggle that saves the resource. Saving is subjected to
130
+ # validation checks. Returns +true+ if the record could be saved.
131
+ def toggle!(attribute)
132
+ toggle(attribute) && save
133
+ end
134
+
135
+ # Refetches the resource
136
+ #
137
+ # This method finds the resource by its primary key (which could be
138
+ # assigned manually) and modifies the object in-place.
139
+ #
140
+ # @example
141
+ # user = User.find(1)
142
+ # # => #<User(users/1) id=1 name="Tobias Fünke">
143
+ # user.name = "Oops"
144
+ # user.reload # Fetched again via GET "/users/1"
145
+ # # => #<User(users/1) id=1 name="Tobias Fünke">
146
+ def reload(options = nil)
147
+ fresh_object = self.class.find(id)
148
+ assign_attributes(fresh_object.attributes)
149
+ self
150
+ end
151
+
152
+ # Uses parsed response to assign attributes and metadata
153
+ #
154
+ # @private
155
+ def load_from_parsed_data(parsed_data)
156
+ data = parsed_data[:data]
157
+ assign_attributes(self.class.parse(data)) if data.any?
158
+ @metadata = parsed_data[:metadata]
159
+ @response_errors = parsed_data[:errors]
160
+ end
161
+
158
162
  module ClassMethods
159
163
  # Create a new chainable scope
160
164
  #
@@ -174,10 +178,8 @@ module Her
174
178
  instance_exec(*args, &code)
175
179
  end
176
180
 
177
- # Add the scope method to the Relation class
178
- Relation.instance_eval do
179
- define_method(name) { |*args| instance_exec(*args, &code) }
180
- end
181
+ # Add the scope method to the default/blank relation
182
+ scoped.define_singleton_method(name) { |*args| instance_exec(*args, &code) }
181
183
  end
182
184
 
183
185
  # @private
@@ -233,7 +235,9 @@ module Her
233
235
  data = parse(parsed_data[:data])
234
236
  metadata = parsed_data[:metadata]
235
237
  response_errors = parsed_data[:errors]
236
- new(data.merge(:_destroyed => response.success?, :metadata => metadata, :response_errors => response_errors))
238
+ record = new(data.merge(:_destroyed => response.success?, :metadata => metadata))
239
+ record.response_errors = response_errors
240
+ record
237
241
  end
238
242
  end
239
243
 
@@ -269,9 +273,9 @@ module Her
269
273
  resource
270
274
  end
271
275
 
272
- private
273
276
  # @private
274
277
  def blank_relation
278
+ @blank_relation ||= superclass.blank_relation.clone.tap { |r| r.parent = self } if superclass.respond_to?(:blank_relation)
275
279
  @blank_relation ||= Relation.new(self)
276
280
  end
277
281
  end
@@ -32,8 +32,18 @@ module Her
32
32
 
33
33
  # @private
34
34
  def to_params(attributes, changes={})
35
- filtered_attributes = attributes.dup.symbolize_keys
35
+ filtered_attributes = attributes.each_with_object({}) do |(key, value), memo|
36
+ case value
37
+ when Her::Model
38
+ when ActiveModel::Serialization
39
+ value = value.serializable_hash.symbolize_keys
40
+ end
41
+
42
+ memo[key.to_sym] = value
43
+ end
44
+
36
45
  filtered_attributes.merge!(embeded_params(attributes))
46
+
37
47
  if her_api.options[:send_only_modified_attributes]
38
48
  filtered_attributes = changes.symbolize_keys.keys.inject({}) do |hash, attribute|
39
49
  hash[attribute] = filtered_attributes[attribute]
@@ -194,17 +204,20 @@ module Her
194
204
 
195
205
  # @private
196
206
  def request_new_object_on_build?
197
- @_her_request_new_object_on_build || (superclass.respond_to?(:request_new_object_on_build?) && superclass.request_new_object_on_build?)
207
+ return @_her_request_new_object_on_build unless @_her_request_new_object_on_build.nil?
208
+ superclass.respond_to?(:request_new_object_on_build?) && superclass.request_new_object_on_build?
198
209
  end
199
210
 
200
211
  # @private
201
212
  def include_root_in_json?
202
- @_her_include_root_in_json || (superclass.respond_to?(:include_root_in_json?) && superclass.include_root_in_json?)
213
+ return @_her_include_root_in_json unless @_her_include_root_in_json.nil?
214
+ superclass.respond_to?(:include_root_in_json?) && superclass.include_root_in_json?
203
215
  end
204
216
 
205
217
  # @private
206
218
  def parse_root_in_json?
207
- @_her_parse_root_in_json || (superclass.respond_to?(:parse_root_in_json?) && superclass.parse_root_in_json?)
219
+ return @_her_parse_root_in_json unless @_her_parse_root_in_json.nil?
220
+ superclass.respond_to?(:parse_root_in_json?) && superclass.parse_root_in_json?
208
221
  end
209
222
  end
210
223
  end
@@ -3,6 +3,7 @@ module Her
3
3
  class Relation
4
4
  # @private
5
5
  attr_accessor :params
6
+ attr_writer :parent
6
7
 
7
8
  # @private
8
9
  def initialize(parent)
@@ -96,7 +97,6 @@ module Her
96
97
  @parent.request(request_params) do |parsed_data, response|
97
98
  if response.success?
98
99
  resource = @parent.new_from_parsed_data(parsed_data)
99
- resource.instance_variable_set(:@changed_attributes, {})
100
100
  resource.run_callbacks :find
101
101
  else
102
102
  return nil