her 0.3.3 → 0.3.4

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.
@@ -11,13 +11,13 @@ module Her
11
11
  #
12
12
  # @user = User.find(1)
13
13
  # p @user # => #<User(/users/1) id=1 name="Tobias Fünke">
14
- def inspect # {{{
14
+ def inspect
15
15
  "#<#{self.class}(#{request_path}) #{@data.inject([]) { |memo, item| key, value = item; memo << "#{key}=#{attribute_for_inspect(value)}"}.join(" ")}>"
16
- end # }}}
16
+ end
17
17
 
18
18
  private
19
19
  # @private
20
- def attribute_for_inspect(value) # {{{
20
+ def attribute_for_inspect(value)
21
21
  if value.is_a?(String) && value.length > 50
22
22
  "#{value[0..50]}...".inspect
23
23
  elsif value.is_a?(Date) || value.is_a?(Time)
@@ -25,32 +25,32 @@ module Her
25
25
  else
26
26
  value.inspect
27
27
  end
28
- end # }}}
28
+ end
29
29
 
30
30
  module ClassMethods
31
31
  # Finds a class at the same level as this one or at the global level.
32
32
  # @private
33
- def nearby_class(name) # {{{
33
+ def nearby_class(name)
34
34
  sibling_class(name) || name.constantize rescue nil
35
- end # }}}
35
+ end
36
36
 
37
37
  protected
38
38
  # Looks for a class at the same level as this one with the given name.
39
39
  # @private
40
- def sibling_class(name) # {{{
40
+ def sibling_class(name)
41
41
  if mod = self.containing_module
42
- "#{mod.name}::#{name}".constantize rescue nil
43
- else
44
- name.constantize rescue nil
42
+ @sibling_class ||= {}
43
+ @sibling_class[mod] ||= {}
44
+ @sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
45
45
  end
46
- end # }}}
46
+ end
47
47
 
48
48
  # If available, returns the containing Module for this class.
49
49
  # @private
50
- def containing_module # {{{
50
+ def containing_module
51
51
  return unless self.name =~ /::/
52
52
  self.name.split("::")[0..-2].join("::").constantize
53
- end # }}}
53
+ end
54
54
  end
55
55
  end
56
56
  end
data/lib/her/model/orm.rb CHANGED
@@ -6,31 +6,44 @@ module Her
6
6
  attr_accessor :data, :metadata, :errors
7
7
 
8
8
  # Initialize a new object with data received from an HTTP request
9
- # @private
10
- def initialize(data={}) # {{{
9
+ def initialize(params={})
11
10
  @data = {}
12
- @metadata = data.delete(:_metadata) || {}
13
- @errors = data.delete(:_errors) || {}
14
-
15
- # Only keep the keys that don't have corresponding writer methods
16
- cleaned_data = data.inject({}) do |memo, item|
17
- key, value = item
18
- send "#{key}=".to_sym, value unless value.nil?
19
- writer_method_defined?(key) ? memo : memo.merge({ key => value })
20
- end
21
- @data.merge! self.class.parse_relationships(cleaned_data)
22
- end # }}}
11
+ @metadata = params.delete(:_metadata) || {}
12
+ @errors = params.delete(:_errors) || {}
13
+
14
+ # Use setter methods first, then translate attributes of relationships
15
+ # into relationship instances, then merge the parsed_data into @data.
16
+ unset_data = Her::Model::ORM.use_setter_methods(self, params)
17
+ parsed_data = self.class.parse_relationships(unset_data)
18
+ @data.update(parsed_data)
19
+ end
23
20
 
24
21
  # Initialize a collection of resources
25
22
  # @private
26
- def self.initialize_collection(klass, parsed_data={}) # {{{
23
+ def self.initialize_collection(klass, parsed_data={})
27
24
  collection_data = parsed_data[:data].map { |item_data| klass.new(item_data) }
28
25
  Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
29
- end # }}}
26
+ end
27
+
28
+ # Use setter methods of model for each key / value pair in params
29
+ # Return key / value pairs for which no setter method was defined on the model
30
+ # @private
31
+ def self.use_setter_methods(model, params)
32
+ setter_method_names = model.class.setter_method_names
33
+ params.inject({}) do |memo, (key, value)|
34
+ setter_method = key.to_s + '='
35
+ if setter_method_names.include?(setter_method)
36
+ model.send(setter_method, value)
37
+ else
38
+ memo[key] = value
39
+ end
40
+ memo
41
+ end
42
+ end
30
43
 
31
44
  # Handles missing methods by routing them through @data
32
45
  # @private
33
- def method_missing(method, *args, &blk) # {{{
46
+ def method_missing(method, *args, &blk)
34
47
  if method.to_s.end_with?('=')
35
48
  @data[method.to_s.chomp('=').to_sym] = args.first
36
49
  elsif method.to_s.end_with?('?')
@@ -40,49 +53,59 @@ module Her
40
53
  else
41
54
  super
42
55
  end
43
- end # }}}
56
+ end
44
57
 
45
58
  # Handles returning true for the cases handled by method_missing
46
- def respond_to?(method, include_private = false) # {{{
59
+ def respond_to?(method, include_private = false)
47
60
  method.to_s.end_with?('=') || method.to_s.end_with?('?') || @data.include?(method) || super
48
- end # }}}
61
+ end
62
+
63
+ # Handles returning true for the accessible attributes
64
+ def has_key?(attribute_name)
65
+ @data.include?(attribute_name)
66
+ end
67
+
68
+ # Handles returning attribute value from data
69
+ def [](attribute_name)
70
+ @data[attribute_name]
71
+ end
49
72
 
50
73
  # Override the method to prevent from returning the object ID (in ruby-1.8.7)
51
74
  # @private
52
- def id # {{{
75
+ def id
53
76
  @data[:id] || super
54
- end # }}}
77
+ end
55
78
 
56
79
  # Return `true` if a resource was not saved yet
57
- def new? # {{{
80
+ def new?
58
81
  !@data.include?(:id)
59
- end # }}}
82
+ end
60
83
 
61
84
  # Return `true` if a resource does not contain errors
62
- def valid? # {{{
85
+ def valid?
63
86
  @errors.empty?
64
- end # }}}
87
+ end
65
88
 
66
89
  # Return `true` if a resource contains errors
67
- def invalid? # {{{
90
+ def invalid?
68
91
  @errors.any?
69
- end # }}}
92
+ end
70
93
 
71
94
  # Return `true` if the other object is also a Her::Model and has matching data
72
- def ==(other) # {{{
95
+ def ==(other)
73
96
  other.is_a?(Her::Model) && @data == other.data
74
- end # }}}
97
+ end
75
98
 
76
99
  # Delegate to the == method
77
- def eql?(other) # {{{
100
+ def eql?(other)
78
101
  self == other
79
- end # }}}
102
+ end
80
103
 
81
104
  # Delegate to @data, allowing models to act correctly in code like:
82
105
  # [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
83
- def hash # {{{
106
+ def hash
84
107
  @data.hash
85
- end # }}}
108
+ end
86
109
 
87
110
  # Save a resource
88
111
  #
@@ -97,7 +120,7 @@ module Her
97
120
  # @user = User.new({ :fullname => "Tobias Fünke" })
98
121
  # @user.save
99
122
  # # Called via POST "/users"
100
- def save # {{{
123
+ def save
101
124
  params = to_params
102
125
  resource = self
103
126
 
@@ -114,10 +137,13 @@ module Her
114
137
  self.data = parsed_data[:data]
115
138
  self.metadata = parsed_data[:metadata]
116
139
  self.errors = parsed_data[:errors]
140
+
141
+ return false if self.errors.any?
117
142
  end
118
143
  end
144
+
119
145
  self
120
- end # }}}
146
+ end
121
147
 
122
148
  # Destroy a resource
123
149
  #
@@ -125,7 +151,7 @@ module Her
125
151
  # @user = User.find(1)
126
152
  # @user.destroy
127
153
  # # Called via DELETE "/users/1"
128
- def destroy # {{{
154
+ def destroy
129
155
  resource = self
130
156
  self.class.wrap_in_hooks(resource, :destroy) do |resource, klass|
131
157
  klass.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data|
@@ -135,32 +161,24 @@ module Her
135
161
  end
136
162
  end
137
163
  self
138
- end # }}}
164
+ end
139
165
 
140
166
  # Convert into a hash of request parameters
141
167
  #
142
168
  # @example
143
169
  # @user.to_params
144
170
  # # => { :id => 1, :name => 'John Smith' }
145
- def to_params # {{{
171
+ def to_params
146
172
  @data.dup
147
- end # }}}
148
-
149
- private
150
-
151
- # @private
152
- def writer_method_defined?(key) # {{{
153
- self.class.instance_methods.include?("#{key}=".to_sym) || # Ruby 1.9
154
- self.class.instance_methods.include?("#{key}=") # Ruby 1.8
155
- end # }}}
173
+ end
156
174
 
157
175
  module ClassMethods
158
176
  # Initialize a collection of resources with raw data from an HTTP request
159
177
  #
160
178
  # @param [Array] parsed_data
161
- def new_collection(parsed_data) # {{{
179
+ def new_collection(parsed_data)
162
180
  Her::Model::ORM.initialize_collection(self, parsed_data)
163
- end # }}}
181
+ end
164
182
 
165
183
  # Fetch specific resource(s) by their ID
166
184
  #
@@ -171,7 +189,7 @@ module Her
171
189
  # @example
172
190
  # @users = User.find([1, 2])
173
191
  # # Fetched via GET "/users/1" and GET "/users/2"
174
- def find(*ids) # {{{
192
+ def find(*ids)
175
193
  params = ids.last.is_a?(Hash) ? ids.pop : {}
176
194
  results = ids.flatten.compact.uniq.map do |id|
177
195
  request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data|
@@ -183,25 +201,25 @@ module Her
183
201
  else
184
202
  results.first
185
203
  end
186
- end # }}}
204
+ end
187
205
 
188
206
  # Fetch a collection of resources
189
207
  #
190
208
  # @example
191
209
  # @users = User.all
192
210
  # # Fetched via GET "/users"
193
- def all(params={}) # {{{
211
+ def all(params={})
194
212
  request(params.merge(:_method => :get, :_path => "#{build_request_path(params)}")) do |parsed_data|
195
213
  new_collection(parsed_data)
196
214
  end
197
- end # }}}
215
+ end
198
216
 
199
217
  # Create a resource and return it
200
218
  #
201
219
  # @example
202
220
  # @user = User.create({ :fullname => "Tobias Fünke" })
203
221
  # # Called via POST "/users/1"
204
- def create(params={}) # {{{
222
+ def create(params={})
205
223
  resource = new(params)
206
224
  wrap_in_hooks(resource, :create, :save) do |resource, klass|
207
225
  params = resource.to_params
@@ -214,28 +232,36 @@ module Her
214
232
  end
215
233
  end
216
234
  resource
217
- end # }}}
235
+ end
218
236
 
219
237
  # Save an existing resource and return it
220
238
  #
221
239
  # @example
222
240
  # @user = User.save_existing(1, { :fullname => "Tobias Fünke" })
223
241
  # # Called via PUT "/users/1"
224
- def save_existing(id, params) # {{{
242
+ def save_existing(id, params)
225
243
  resource = new(params.merge(:id => id))
226
244
  resource.save
227
- end # }}}
245
+ end
228
246
 
229
247
  # Destroy an existing resource
230
248
  #
231
249
  # @example
232
250
  # User.destroy_existing(1)
233
251
  # # Called via DELETE "/users/1"
234
- def destroy_existing(id, params={}) # {{{
252
+ def destroy_existing(id, params={})
235
253
  request(params.merge(:_method => :delete, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data|
236
254
  new(parsed_data[:data])
237
255
  end
238
- end # }}}
256
+ end
257
+
258
+ # @private
259
+ def setter_method_names
260
+ @setter_method_names ||= instance_methods.inject(Set.new) do |memo, method_name|
261
+ memo << method_name.to_s if method_name.to_s.end_with?('=')
262
+ memo
263
+ end
264
+ end
239
265
  end
240
266
  end
241
267
  end
@@ -11,9 +11,9 @@ module Her
11
11
  # end
12
12
  #
13
13
  # User.find(1) # Fetched via GET /utilisateurs/1
14
- def request_path # {{{
14
+ def request_path
15
15
  self.class.build_request_path(@data.dup)
16
- end # }}}
16
+ end
17
17
 
18
18
  module ClassMethods
19
19
  # Defines a custom collection path for the resource
@@ -23,7 +23,7 @@ module Her
23
23
  # include Her::Model
24
24
  # collection_path "/users"
25
25
  # end
26
- def collection_path(path=nil) # {{{
26
+ def collection_path(path=nil)
27
27
  @her_collection_path ||= begin
28
28
  superclass.collection_path.dup if superclass.respond_to?(:collection_path)
29
29
  end
@@ -31,7 +31,7 @@ module Her
31
31
  return @her_collection_path unless path
32
32
  @her_resource_path = "#{path}/:id"
33
33
  @her_collection_path = path
34
- end # }}}
34
+ end
35
35
 
36
36
  # Defines a custom resource path for the resource
37
37
  #
@@ -40,14 +40,14 @@ module Her
40
40
  # include Her::Model
41
41
  # resource_path "/users/:id"
42
42
  # end
43
- def resource_path(path=nil) # {{{
43
+ def resource_path(path=nil)
44
44
  @her_resource_path ||= begin
45
45
  superclass.resource_path.dup if superclass.respond_to?(:resource_path)
46
46
  end
47
47
 
48
48
  return @her_resource_path unless path
49
49
  @her_resource_path = path
50
- end # }}}
50
+ end
51
51
 
52
52
  # Return a custom path based on the collection path and variable parameters
53
53
  #
@@ -58,7 +58,7 @@ module Her
58
58
  # end
59
59
  #
60
60
  # User.all # Fetched via GET /utilisateurs
61
- def build_request_path(path=nil, parameters={}) # {{{
61
+ def build_request_path(path=nil, parameters={})
62
62
  unless path.is_a?(String)
63
63
  parameters = path || {}
64
64
  path = parameters.include?(:id) ? resource_path : collection_path
@@ -68,7 +68,7 @@ module Her
68
68
  # Look for :key or :_key, otherwise raise an exception
69
69
  parameters.delete($1.to_sym) || parameters.delete("_#{$1}".to_sym) || raise(Her::Errors::PathError.new("Missing :_#{$1} parameter to build the request path (#{path})."))
70
70
  end
71
- end # }}}
71
+ end
72
72
  end
73
73
  end
74
74
  end
@@ -4,8 +4,9 @@ module Her
4
4
  module Relationships
5
5
  # Return @her_relationships, lazily initialized with copy of the
6
6
  # superclass' her_relationships, or an empty hash.
7
+ #
7
8
  # @private
8
- def relationships # {{{
9
+ def relationships
9
10
  @her_relationships ||= begin
10
11
  if superclass.respond_to?(:relationships)
11
12
  superclass.relationships.dup
@@ -13,16 +14,17 @@ module Her
13
14
  {}
14
15
  end
15
16
  end
16
- end # }}}
17
+ end
17
18
 
18
19
  # Parse relationships data after initializing a new object
20
+ #
19
21
  # @private
20
- def parse_relationships(data) # {{{
22
+ def parse_relationships(data)
21
23
  relationships.each_pair do |type, definitions|
22
24
  definitions.each do |relationship|
23
25
  name = relationship[:name]
26
+ next unless data[name]
24
27
  klass = self.nearby_class(relationship[:class_name])
25
- next if !data.include?(name) or data[name].nil?
26
28
  data[name] = case type
27
29
  when :has_many
28
30
  Her::Model::ORM.initialize_collection(klass, :data => data[name])
@@ -34,7 +36,7 @@ module Her
34
36
  end
35
37
  end
36
38
  data
37
- end # }}}
39
+ end
38
40
 
39
41
  # Define an *has_many* relationship.
40
42
  #
@@ -54,7 +56,7 @@ module Her
54
56
  # @user = User.find(1)
55
57
  # @user.articles # => [#<Article(articles/2) id=2 title="Hello world.">]
56
58
  # # Fetched via GET "/users/1/articles"
57
- def has_many(name, attrs={}) # {{{
59
+ def has_many(name, attrs={})
58
60
  attrs = {
59
61
  :class_name => name.to_s.classify,
60
62
  :name => name,
@@ -71,7 +73,7 @@ module Her
71
73
  @data[name] ||= klass.get_collection("#{self.class.build_request_path(:id => id)}#{attrs[:path]}")
72
74
  end
73
75
  end
74
- end # }}}
76
+ end
75
77
 
76
78
  # Define an *has_one* relationship.
77
79
  #
@@ -91,7 +93,7 @@ module Her
91
93
  # @user = User.find(1)
92
94
  # @user.organization # => #<Organization(organizations/2) id=2 name="Foobar Inc.">
93
95
  # # Fetched via GET "/users/1/organization"
94
- def has_one(name, attrs={}) # {{{
96
+ def has_one(name, attrs={})
95
97
  attrs = {
96
98
  :class_name => name.to_s.classify,
97
99
  :name => name,
@@ -108,7 +110,7 @@ module Her
108
110
  @data[name] ||= klass.get_resource("#{self.class.build_request_path(:id => id)}#{attrs[:path]}")
109
111
  end
110
112
  end
111
- end # }}}
113
+ end
112
114
 
113
115
  # Define a *belongs_to* relationship.
114
116
  #
@@ -128,7 +130,7 @@ module Her
128
130
  # @user = User.find(1)
129
131
  # @user.team # => #<Team(teams/2) id=2 name="Developers">
130
132
  # # Fetched via GET "/teams/2"
131
- def belongs_to(name, attrs={}) # {{{
133
+ def belongs_to(name, attrs={})
132
134
  attrs = {
133
135
  :class_name => name.to_s.classify,
134
136
  :name => name,
@@ -146,17 +148,17 @@ module Her
146
148
  @data[name] ||= klass.get_resource("#{klass.build_request_path(:id => @data[attrs[:foreign_key].to_sym])}")
147
149
  end
148
150
  end
149
- end # }}}
151
+ end
150
152
 
151
153
  # @private
152
- def relationship_accessor(type, attrs) # {{{
154
+ def relationship_accessor(type, attrs)
153
155
  name = attrs[:name]
154
156
  class_name = attrs[:class_name]
155
157
  define_method(name) do
156
158
  klass = self.class.nearby_class(attrs[:class_name])
157
159
  @data[name] ||= klass.get_resource("#{klass.build_request_path(attrs[:path], :id => @data[attrs[:foreign_key].to_sym])}")
158
160
  end
159
- end # }}}
161
+ end
160
162
  end
161
163
  end
162
164
  end