her 0.10.0 → 0.10.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.
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