her 0.3.6 → 0.3.7
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.
- data/.gitignore +1 -0
- data/CONTRIBUTING.md +26 -0
- data/FEATURES.md +296 -0
- data/MIDDLEWARE.md +183 -0
- data/README.md +20 -482
- data/TESTING.md +88 -0
- data/her.gemspec +2 -2
- data/lib/her/api.rb +2 -2
- data/lib/her/collection.rb +3 -3
- data/lib/her/middleware/first_level_parse_json.rb +1 -1
- data/lib/her/model/http.rb +5 -1
- data/lib/her/model/orm.rb +12 -0
- data/lib/her/version.rb +1 -1
- data/spec/collection_spec.rb +27 -0
- data/spec/middleware/first_level_parse_json_spec.rb +12 -5
- data/spec/model/orm_spec.rb +25 -0
- metadata +15 -9
data/TESTING.md
ADDED
@@ -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
|
+
```
|
data/her.gemspec
CHANGED
@@ -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
|
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", "~>
|
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
|
|
data/lib/her/api.rb
CHANGED
@@ -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
|
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
|
data/lib/her/collection.rb
CHANGED
@@ -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,
|
data/lib/her/model/http.rb
CHANGED
@@ -5,7 +5,11 @@ module Her
|
|
5
5
|
# Automatically inherit a superclass' api
|
6
6
|
def her_api
|
7
7
|
@her_api ||= begin
|
8
|
-
|
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
|
|
data/lib/her/model/orm.rb
CHANGED
@@ -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)
|
data/lib/her/version.rb
CHANGED
@@ -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(:
|
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(
|
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 =>
|
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
|
data/spec/model/orm_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|
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
|
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: '
|
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: '
|
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: -
|
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: -
|
289
|
+
hash: -1556423297690229107
|
285
290
|
requirements: []
|
286
291
|
rubyforge_project:
|
287
|
-
rubygems_version: 1.8.
|
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
|