her 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,88 @@
1
+ # Testing Her models
2
+
3
+ Suppose we have these two models bound to your API:
4
+
5
+ ```ruby
6
+ # app/models/user.rb
7
+ class User
8
+ include Her::Model
9
+ custom_get :popular
10
+ end
11
+
12
+ # app/models/post.rb
13
+ class Post
14
+ include Her::Model
15
+ custom_get :recent, :archived
16
+ end
17
+ ```
18
+
19
+ In order to test them, we’ll have to stub the remote API requests. With [RSpec](https://github.com/rspec/rspec-core), we can do this like so:
20
+
21
+ ```ruby
22
+ # spec/spec_helper.rb
23
+ RSpec.configure do |config|
24
+ config.include(Module.new do
25
+ def stub_api_for(klass)
26
+ klass.uses_api (api = Her::API.new)
27
+
28
+ # Here, you would customize this for your own API (URL, middleware, etc)
29
+ # like you have done in your application’s initializer
30
+ api.setup :url => "http://api.example.com" do |connection|
31
+ connection.use Her::Middleware::FirstLevelParseJSON
32
+ connection.use Faraday::Request::UrlEncoded
33
+ connection.adapter(:test) { |s| yield(s) }
34
+ end
35
+ end
36
+ end)
37
+ end
38
+ ```
39
+
40
+ Then, in your tests, we can specify what (fake) HTTP requests will return:
41
+
42
+ ```ruby
43
+ # spec/models/user.rb
44
+ describe User do
45
+ before do
46
+ stub_api_for(User) do |stub|
47
+ stub.get("/users/popular") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
48
+ end
49
+ end
50
+
51
+ describe :popular do
52
+ subject { User.popular }
53
+ its(:length) { should == 2 }
54
+ its(:errors) { should be_empty }
55
+ end
56
+ end
57
+ ```
58
+
59
+ We can redefine the API for a model as many times as we want, like for more complex tests:
60
+
61
+ ```ruby
62
+ # spec/models/user.rb
63
+ describe Post do
64
+ describe :recent do
65
+ before do
66
+ stub_api_for(Post) do |stub|
67
+ stub.get("/posts/recent") { |env| [200, {}, [{ :id => 1 }, { :id => 2 }].to_json] }
68
+ end
69
+ end
70
+
71
+ subject { Post.recent }
72
+ its(:length) { should == 2 }
73
+ its(:errors) { should be_empty }
74
+ end
75
+
76
+ describe :archived do
77
+ before do
78
+ stub_api_for(Post) do |stub|
79
+ stub.get("/posts/archived") { |env| [200, {}, [{ :id => 1 }, { :id => 2 }].to_json] }
80
+ end
81
+ end
82
+
83
+ subject { Post.archived }
84
+ its(:length) { should == 2 }
85
+ its(:errors) { should be_empty }
86
+ end
87
+ end
88
+ ```
@@ -17,13 +17,13 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_development_dependency "rake", "~> 0.9.2"
20
+ s.add_development_dependency "rake", "~> 10.0"
21
21
  s.add_development_dependency "rspec", "~> 2.11"
22
22
  s.add_development_dependency "yard", "~> 0.8"
23
23
  s.add_development_dependency "redcarpet", "~> 2.1"
24
24
  s.add_development_dependency "mocha", "~> 0.12"
25
25
  s.add_development_dependency "guard", "~> 1.2"
26
- s.add_development_dependency "guard-rspec", "~> 1.2"
26
+ s.add_development_dependency "guard-rspec", "~> 2.3"
27
27
  s.add_development_dependency "rb-fsevent", "~> 0.9"
28
28
  s.add_development_dependency "growl", "~> 1.0"
29
29
 
@@ -39,7 +39,7 @@ module Her
39
39
  # class MyCustomParser < Faraday::Response::Middleware
40
40
  # def on_complete(env)
41
41
  # json = JSON.parse(env[:body], :symbolize_names => true)
42
- # errors = json.delete(:errors) || []
42
+ # errors = json.delete(:errors) || {}
43
43
  # metadata = json.delete(:metadata) || []
44
44
  # env[:body] = { :data => json, :errors => errors, :metadata => metadata }
45
45
  # end
@@ -59,7 +59,7 @@ module Her
59
59
  end
60
60
 
61
61
  # Define a custom parsing procedure. The procedure is passed the response object and is
62
- # expected to return a hash with three keys: a main data Hash, an errors Array
62
+ # expected to return a hash with three keys: a main data Hash, an errors Hash
63
63
  # and a metadata Hash.
64
64
  #
65
65
  # @private
@@ -3,10 +3,10 @@ module Her
3
3
  attr_reader :metadata, :errors
4
4
 
5
5
  # @private
6
- def initialize(items=[], metadata={}, errors=[])
6
+ def initialize(items=[], metadata={}, errors={})
7
7
  super(items)
8
- @metadata = metadata || {}
9
- @errors = errors || []
8
+ @metadata = metadata
9
+ @errors = errors
10
10
  end
11
11
  end
12
12
  end
@@ -8,7 +8,7 @@ module Her
8
8
  # @return [Mixed] the parsed response
9
9
  def parse(body)
10
10
  json = MultiJson.load(body, :symbolize_keys => true)
11
- errors = json.delete(:errors) || []
11
+ errors = json.delete(:errors) || {}
12
12
  metadata = json.delete(:metadata) || []
13
13
  {
14
14
  :data => json,
@@ -5,7 +5,11 @@ module Her
5
5
  # Automatically inherit a superclass' api
6
6
  def her_api
7
7
  @her_api ||= begin
8
- superclass.her_api if superclass.respond_to?(:her_api)
8
+ if superclass.respond_to?(:her_api)
9
+ superclass.her_api
10
+ else
11
+ Her::API.default_api
12
+ end
9
13
  end
10
14
  end
11
15
 
@@ -4,6 +4,8 @@ module Her
4
4
  module ORM
5
5
  extend ActiveSupport::Concern
6
6
  attr_accessor :data, :metadata, :errors
7
+ alias :attributes :data
8
+ alias :attributes= :data=
7
9
 
8
10
  # Initialize a new object with data received from an HTTP request
9
11
  def initialize(params={})
@@ -35,6 +37,9 @@ module Her
35
37
  if setter_method_names.include?(setter_method)
36
38
  model.send(setter_method, value)
37
39
  else
40
+ if key.is_a?(String)
41
+ key = key.to_sym
42
+ end
38
43
  memo[key] = value
39
44
  end
40
45
  memo
@@ -60,6 +65,13 @@ module Her
60
65
  method.to_s.end_with?('=') || method.to_s.end_with?('?') || @data.include?(method) || super
61
66
  end
62
67
 
68
+ # Assign new data to an instance
69
+ def assign_data(new_data)
70
+ new_data = Her::Model::ORM.use_setter_methods(self, new_data)
71
+ @data.update new_data
72
+ end
73
+ alias :assign_attributes :assign_data
74
+
63
75
  # Handles returning true for the accessible attributes
64
76
  def has_data?(attribute_name)
65
77
  @data.include?(attribute_name)
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.3.6"
2
+ VERSION = "0.3.7"
3
3
  end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Her::Collection do
4
+
5
+ let(:items) { [1,2,3,4] }
6
+ let(:metadata) { {:name => 'Testname'} }
7
+ let(:errors) { {:name => ['not_present']} }
8
+
9
+ describe "#new" do
10
+ context "without parameters" do
11
+
12
+ subject { Her::Collection.new }
13
+
14
+ it { should eq([])}
15
+ its(:metadata) { should eq({})}
16
+ its(:errors) { should eq({})}
17
+ end
18
+
19
+ context "with parameters" do
20
+ subject { Her::Collection.new(items, metadata, errors)}
21
+
22
+ it { should eq([1,2,3,4])}
23
+ its(:metadata) { should eq({:name => 'Testname'})}
24
+ its(:errors) { should eq({:name => ['not_present']})}
25
+ end
26
+ end
27
+ end
@@ -3,23 +3,30 @@ require "spec_helper"
3
3
 
4
4
  describe Her::Middleware::FirstLevelParseJSON do
5
5
  subject { described_class.new }
6
- let(:body) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"errors\": 2, \"metadata\": 3}" }
6
+ let(:body_without_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"metadata\": 3}" }
7
+ let(:body_with_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"errors\": { \"name\": [ \"not_valid\", \"should_be_present\" ] }, \"metadata\": 3}" }
7
8
 
8
9
  it "parses body as json" do
9
- subject.parse(body).tap do |json|
10
+ subject.parse(body_without_errors).tap do |json|
10
11
  json[:data].should == { :id => 1, :name => "Tobias Fünke" }
11
- json[:errors].should == 2
12
12
  json[:metadata].should == 3
13
13
  end
14
14
  end
15
15
 
16
16
  it "parses :body key as json in the env hash" do
17
- env = { :body => body }
17
+ env = { :body => body_without_errors }
18
18
  subject.on_complete(env)
19
19
  env[:body].tap do |json|
20
20
  json[:data].should == { :id => 1, :name => "Tobias Fünke" }
21
- json[:errors].should == 2
22
21
  json[:metadata].should == 3
23
22
  end
24
23
  end
24
+
25
+ it 'ensures the errors are a hash if there are no errors' do
26
+ subject.parse(body_without_errors)[:errors].should eq({})
27
+ end
28
+
29
+ it 'ensures the errors are a hash if there are no errors' do
30
+ subject.parse(body_with_errors)[:errors].should eq({:name => [ 'not_valid', 'should_be_present']})
31
+ end
25
32
  end
@@ -49,6 +49,11 @@ describe Her::Model::ORM do
49
49
  @existing_user.new?.should be_false
50
50
  end
51
51
 
52
+ it "accepts new resource with strings as hash keys" do
53
+ @new_user = Foo::User.new('fullname' => "Tobias Fünke")
54
+ @new_user.fullname.should == "Tobias Fünke"
55
+ end
56
+
52
57
  it "handles method missing for getter" do
53
58
  @new_user = Foo::User.new(:fullname => 'Mayonegg')
54
59
  lambda { @new_user.unknown_method_for_a_user }.should raise_error(NoMethodError)
@@ -314,6 +319,26 @@ describe Her::Model::ORM do
314
319
  end
315
320
  end
316
321
 
322
+ context "assigning new resource data" do
323
+ before do
324
+ Her::API.setup :url => "https://api.example.com" do |builder|
325
+ builder.use Her::Middleware::FirstLevelParseJSON
326
+ builder.use Faraday::Request::UrlEncoded
327
+ builder.adapter :test do |stub|
328
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :active => true }.to_json] }
329
+ end
330
+ end
331
+
332
+ spawn_model "Foo::User"
333
+ @user = Foo::User.find(1)
334
+ end
335
+
336
+ it "handles data update through #assign_attributes" do
337
+ @user.assign_attributes :active => true
338
+ @user.should be_active
339
+ end
340
+ end
341
+
317
342
  context "deleting resources" do
318
343
  before do
319
344
  Her::API.setup :url => "https://api.example.com" do |builder|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: her
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.3.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-05 00:00:00.000000000 Z
12
+ date: 2012-12-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.9.2
21
+ version: '10.0'
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.9.2
29
+ version: '10.0'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: rspec
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -114,7 +114,7 @@ dependencies:
114
114
  requirements:
115
115
  - - ~>
116
116
  - !ruby/object:Gem::Version
117
- version: '1.2'
117
+ version: '2.3'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
@@ -122,7 +122,7 @@ dependencies:
122
122
  requirements:
123
123
  - - ~>
124
124
  - !ruby/object:Gem::Version
125
- version: '1.2'
125
+ version: '2.3'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rb-fsevent
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -213,11 +213,15 @@ files:
213
213
  - .gitignore
214
214
  - .rspec
215
215
  - .travis.yml
216
+ - CONTRIBUTING.md
217
+ - FEATURES.md
216
218
  - Gemfile
217
219
  - Guardfile
218
220
  - LICENSE
221
+ - MIDDLEWARE.md
219
222
  - README.md
220
223
  - Rakefile
224
+ - TESTING.md
221
225
  - UPGRADE.md
222
226
  - examples/twitter-oauth/Gemfile
223
227
  - examples/twitter-oauth/app.rb
@@ -246,6 +250,7 @@ files:
246
250
  - lib/her/model/relationships.rb
247
251
  - lib/her/version.rb
248
252
  - spec/api_spec.rb
253
+ - spec/collection_spec.rb
249
254
  - spec/middleware/accept_json_spec.rb
250
255
  - spec/middleware/first_level_parse_json_spec.rb
251
256
  - spec/middleware/second_level_parse_json_spec.rb
@@ -272,7 +277,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
272
277
  version: '0'
273
278
  segments:
274
279
  - 0
275
- hash: -1080420939636029022
280
+ hash: -1556423297690229107
276
281
  required_rubygems_version: !ruby/object:Gem::Requirement
277
282
  none: false
278
283
  requirements:
@@ -281,16 +286,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
281
286
  version: '0'
282
287
  segments:
283
288
  - 0
284
- hash: -1080420939636029022
289
+ hash: -1556423297690229107
285
290
  requirements: []
286
291
  rubyforge_project:
287
- rubygems_version: 1.8.23
292
+ rubygems_version: 1.8.24
288
293
  signing_key:
289
294
  specification_version: 3
290
295
  summary: A simple Representational State Transfer-based Hypertext Transfer Protocol-powered
291
296
  Object Relational Mapper. Her?
292
297
  test_files:
293
298
  - spec/api_spec.rb
299
+ - spec/collection_spec.rb
294
300
  - spec/middleware/accept_json_spec.rb
295
301
  - spec/middleware/first_level_parse_json_spec.rb
296
302
  - spec/middleware/second_level_parse_json_spec.rb