her 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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