api_resource 0.2.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.
- data/.document +5 -0
- data/.rspec +3 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +152 -0
- data/Guardfile +22 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/api_resource.gemspec +154 -0
- data/lib/api_resource.rb +129 -0
- data/lib/api_resource/association_activation.rb +19 -0
- data/lib/api_resource/associations.rb +169 -0
- data/lib/api_resource/associations/association_proxy.rb +115 -0
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +16 -0
- data/lib/api_resource/associations/dynamic_resource_scope.rb +23 -0
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +16 -0
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +24 -0
- data/lib/api_resource/associations/multi_argument_resource_scope.rb +15 -0
- data/lib/api_resource/associations/multi_object_proxy.rb +73 -0
- data/lib/api_resource/associations/related_object_hash.rb +12 -0
- data/lib/api_resource/associations/relation_scope.rb +30 -0
- data/lib/api_resource/associations/resource_scope.rb +34 -0
- data/lib/api_resource/associations/scope.rb +107 -0
- data/lib/api_resource/associations/single_object_proxy.rb +81 -0
- data/lib/api_resource/attributes.rb +162 -0
- data/lib/api_resource/base.rb +587 -0
- data/lib/api_resource/callbacks.rb +49 -0
- data/lib/api_resource/connection.rb +171 -0
- data/lib/api_resource/core_extensions.rb +7 -0
- data/lib/api_resource/custom_methods.rb +119 -0
- data/lib/api_resource/exceptions.rb +87 -0
- data/lib/api_resource/formats.rb +14 -0
- data/lib/api_resource/formats/json_format.rb +25 -0
- data/lib/api_resource/formats/xml_format.rb +36 -0
- data/lib/api_resource/local.rb +12 -0
- data/lib/api_resource/log_subscriber.rb +15 -0
- data/lib/api_resource/mocks.rb +269 -0
- data/lib/api_resource/model_errors.rb +86 -0
- data/lib/api_resource/observing.rb +29 -0
- data/lib/api_resource/railtie.rb +22 -0
- data/lib/api_resource/scopes.rb +45 -0
- data/spec/lib/associations_spec.rb +656 -0
- data/spec/lib/attributes_spec.rb +121 -0
- data/spec/lib/base_spec.rb +504 -0
- data/spec/lib/callbacks_spec.rb +68 -0
- data/spec/lib/connection_spec.rb +76 -0
- data/spec/lib/local_spec.rb +20 -0
- data/spec/lib/mocks_spec.rb +28 -0
- data/spec/lib/model_errors_spec.rb +29 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/mocks/association_mocks.rb +46 -0
- data/spec/support/mocks/error_resource_mocks.rb +21 -0
- data/spec/support/mocks/test_resource_mocks.rb +43 -0
- data/spec/support/requests/association_requests.rb +14 -0
- data/spec/support/requests/error_resource_requests.rb +25 -0
- data/spec/support/requests/test_resource_requests.rb +31 -0
- data/spec/support/test_resource.rb +64 -0
- metadata +334 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
include ApiResource
|
4
|
+
|
5
|
+
describe "Should put callbacks around save, create, update, and destroy by default" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
# This defines all the callbacks to check and see if they are fired
|
9
|
+
TestResource.class_eval <<-EOE, __FILE__, __LINE__ + 1
|
10
|
+
attr_accessor :s_val, :c_val, :u_val, :d_val
|
11
|
+
before_save :bs_cb; after_save :as_cb
|
12
|
+
before_create :bc_cb; after_create :ac_cb
|
13
|
+
before_update :bu_cb; after_update :au_cb
|
14
|
+
before_destroy :bd_cb; after_destroy :ad_cb
|
15
|
+
|
16
|
+
def bs_cb
|
17
|
+
@s_val = 1
|
18
|
+
end
|
19
|
+
def as_cb
|
20
|
+
@s_val += 1
|
21
|
+
end
|
22
|
+
def bc_cb
|
23
|
+
@c_val = 1
|
24
|
+
end
|
25
|
+
def ac_cb
|
26
|
+
@c_val += 1
|
27
|
+
end
|
28
|
+
def bu_cb
|
29
|
+
@u_val = 1
|
30
|
+
end
|
31
|
+
def au_cb
|
32
|
+
@u_val += 1
|
33
|
+
end
|
34
|
+
def bd_cb
|
35
|
+
@d_val = 1
|
36
|
+
end
|
37
|
+
def ad_cb
|
38
|
+
@d_val += 1
|
39
|
+
end
|
40
|
+
EOE
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should fire save and create callbacks when saving a new record" do
|
44
|
+
tr = TestResource.new(:name => "Ethan", :age => 20)
|
45
|
+
tr.save.should be_true
|
46
|
+
tr.s_val.should eql(2)
|
47
|
+
tr.c_val.should eql(2)
|
48
|
+
tr.u_val.should be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should fire save and update callbacks when updating a record" do
|
52
|
+
tr = TestResource.new(:id => 1, :name => "Ethan", :age => 20)
|
53
|
+
tr.name = "Test"
|
54
|
+
tr.age = 21
|
55
|
+
tr.save.should be_true
|
56
|
+
tr.s_val.should eql(2)
|
57
|
+
tr.c_val.should be_nil
|
58
|
+
tr.u_val.should eql(2)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should only fire destroy callbacks when destroying a record" do
|
62
|
+
tr = TestResource.new(:id => 1, :name => "Ethan", :age => 20)
|
63
|
+
tr.destroy.should be_true
|
64
|
+
tr.d_val.should eql(2)
|
65
|
+
tr.s_val.should be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
include ApiResource
|
4
|
+
|
5
|
+
describe Connection do
|
6
|
+
|
7
|
+
it "should be able to set the token directly on ApiResource" do
|
8
|
+
ApiResource.token = "123"
|
9
|
+
ApiResource::Base.token.should eql "123"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to set a default token value, which is passed through each request" do
|
13
|
+
TestResource.connection.expects(:get).with("/test_resources/1.json", "Lifebooker-Token" => "abc")
|
14
|
+
ApiResource::Base.token = "abc"
|
15
|
+
TestResource.find(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should be able to set a token for a given block" do
|
19
|
+
ApiResource::Base.token = "123456"
|
20
|
+
begin
|
21
|
+
ApiResource.with_token("testing") do
|
22
|
+
ApiResource::Base.token.should eql "testing"
|
23
|
+
raise "AAAH"
|
24
|
+
end
|
25
|
+
rescue => e
|
26
|
+
# should still reset the token
|
27
|
+
end
|
28
|
+
ApiResource::Base.token.should eql "123456"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should provider a method to regenerate its connection" do
|
32
|
+
conn = ApiResource::Base.connection
|
33
|
+
conn.should be ApiResource::Base.connection
|
34
|
+
ApiResource.reset_connection
|
35
|
+
conn.should_not be ApiResource::Base.connection
|
36
|
+
end
|
37
|
+
|
38
|
+
context "No Mocks" do
|
39
|
+
before(:all) do
|
40
|
+
ApiResource::Mocks.remove
|
41
|
+
end
|
42
|
+
after(:all) do
|
43
|
+
ApiResource::Mocks.init
|
44
|
+
ApiResource.timeout = 10
|
45
|
+
ApiResource.open_timeout = 10
|
46
|
+
end
|
47
|
+
it "should be able to set a timeout for its connection" do
|
48
|
+
ApiResource.timeout = 1
|
49
|
+
ApiResource.timeout.should eql 1
|
50
|
+
ApiResource.open_timeout = 1
|
51
|
+
ApiResource.open_timeout.should eql 1
|
52
|
+
|
53
|
+
ApiResource::Base.connection.send(:http, "/test").options[:timeout].should eql 1
|
54
|
+
ApiResource::Base.connection.send(:http, "/test").options[:open_timeout].should eql 1
|
55
|
+
|
56
|
+
ApiResource.timeout = 100
|
57
|
+
ApiResource::Base.connection.send(:http, "/test").options[:timeout].should eql 100
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should time out if RestClient takes too long" do
|
62
|
+
|
63
|
+
# hopefully google won't actually respond this fast :)
|
64
|
+
ApiResource.timeout = 0.001
|
65
|
+
ApiResource::Base.site = "http://www.google.com"
|
66
|
+
lambda{
|
67
|
+
ApiResource::Base.connection.get("/")
|
68
|
+
}.should raise_error(ApiResource::RequestTimeout)
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
include ApiResource
|
5
|
+
|
6
|
+
describe "Local" do
|
7
|
+
|
8
|
+
it "should not go to the server to fetch a resource definition" do
|
9
|
+
ApiResource::Connection.any_instance.expects(:get).never
|
10
|
+
class MyTestResource < ApiResource::Local
|
11
|
+
scope :test, {:test => true}
|
12
|
+
end
|
13
|
+
mtr = MyTestResource.new
|
14
|
+
# should still have scopes
|
15
|
+
MyTestResource.reload_class_attributes
|
16
|
+
mtr.scopes.should_not be_blank
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
include ApiResource
|
5
|
+
|
6
|
+
describe Mocks do
|
7
|
+
|
8
|
+
# we set up the mocks in spec helper, so we can just assert this
|
9
|
+
it "should hijack the connection" do
|
10
|
+
ApiResource::Mocks::Interface.any_instance.expects(:get).once.returns(
|
11
|
+
ApiResource::Mocks::MockResponse.new("", {:headers => {"Content-type" => "application/json"}, :status_code => 200})
|
12
|
+
)
|
13
|
+
TestResource.reload_class_attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow the user to raise errors for invalid responsed" do
|
17
|
+
old_err_status = ApiResource.raise_missing_definition_error
|
18
|
+
ApiResource::Base.raise_missing_definition_error = true
|
19
|
+
|
20
|
+
lambda {
|
21
|
+
class MyNewInvalidResource < ApiResource::Base; end
|
22
|
+
MyNewInvalidResource.new
|
23
|
+
}.should raise_error(ApiResource::ResourceNotFound)
|
24
|
+
|
25
|
+
ApiResource.raise_missing_definition_error = old_err_status
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
include ApiResource
|
4
|
+
|
5
|
+
describe "Saving Resources with errors" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
ErrorResource.include_root_in_json = true
|
9
|
+
end
|
10
|
+
|
11
|
+
context "Remote Errors" do
|
12
|
+
|
13
|
+
it "should be able to handle errors as a hash" do
|
14
|
+
t = ErrorResource.new(:name => "Ethan", :age => 12)
|
15
|
+
t.save.should be_false
|
16
|
+
t.errors.should_not be_nil
|
17
|
+
t.errors['name'].should_not be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to handle errors as full messages" do
|
21
|
+
t = ErrorFullMessageResource.new(:name => "Ethan", :age => 12)
|
22
|
+
t.save.should be_false
|
23
|
+
t.errors.should_not be_nil
|
24
|
+
t.errors['name'].should_not be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'bundler'
|
5
|
+
require 'api_resource'
|
6
|
+
require 'simplecov'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Bundler.require(:default, :development)
|
11
|
+
Debugger.start
|
12
|
+
|
13
|
+
SimpleCov.start do
|
14
|
+
add_filter "/spec/"
|
15
|
+
end
|
16
|
+
|
17
|
+
SimpleCov.at_exit do
|
18
|
+
SimpleCov.result.format!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Requires supporting files with custom matchers and macros, etc,
|
22
|
+
# in ./support/ and its subdirectories.
|
23
|
+
#ApiResource.load_mocks_and_factories
|
24
|
+
ApiResource.site = 'http://localhost:3000'
|
25
|
+
ApiResource.format = :json
|
26
|
+
ApiResource.load_mocks_and_factories
|
27
|
+
|
28
|
+
ApiResource.logger.level = Log4r::INFO
|
29
|
+
|
30
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
RSpec.configure do |config|
|
35
|
+
config.mock_with :mocha
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
include ApiResource
|
2
|
+
|
3
|
+
Mocks.define do
|
4
|
+
|
5
|
+
endpoint('/single_object_association') do
|
6
|
+
get(HashDealer.roll(:test_association_resource), :params => {})
|
7
|
+
get(HashDealer.roll(:active_test_association_resource), :params => {:active => true})
|
8
|
+
get(HashDealer.roll(:active_birthday_test_association_resource), :params => {:active => true, :birthday => true})
|
9
|
+
end
|
10
|
+
|
11
|
+
endpoint('/multi_object_association') do
|
12
|
+
get((0..4).to_a.collect{HashDealer.roll(:test_association_resource)}, :params => {})
|
13
|
+
get((0..4).to_a.collect{HashDealer.roll(:active_test_association_resource)}, :params => {:active => true})
|
14
|
+
get((0..4).to_a.collect{HashDealer.roll(:active_test_association_resource)}, :params => {:active => false})
|
15
|
+
get((0..4).to_a.collect{HashDealer.roll(:active_birthday_test_association_resource)}, :params => {:active => true, :birthday => true})
|
16
|
+
end
|
17
|
+
|
18
|
+
endpoint("/has_one_objects/new") do
|
19
|
+
get({})
|
20
|
+
end
|
21
|
+
|
22
|
+
endpoint("/has_many_objects/new") do
|
23
|
+
get({
|
24
|
+
"attributes" => {
|
25
|
+
"public" => ["name"]
|
26
|
+
}
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
endpoint("/belongs_to_objects/new") do
|
31
|
+
get({})
|
32
|
+
end
|
33
|
+
|
34
|
+
endpoint("/test_associations/new") do
|
35
|
+
get({})
|
36
|
+
end
|
37
|
+
|
38
|
+
endpoint("/inner_classes/new") do
|
39
|
+
get({})
|
40
|
+
end
|
41
|
+
|
42
|
+
endpoint("/childern/new") do
|
43
|
+
get({})
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
include ApiResource
|
2
|
+
|
3
|
+
Mocks.define do
|
4
|
+
|
5
|
+
endpoint("/error_resources/new") do
|
6
|
+
get(HashDealer.roll(:new_error_resource))
|
7
|
+
end
|
8
|
+
|
9
|
+
endpoint("/error_resources") do
|
10
|
+
post(HashDealer.roll(:error_resource_errors), :params => {:error_resource => HashDealer.roll(:error_resource).matcher}, :status_code => 422)
|
11
|
+
end
|
12
|
+
|
13
|
+
endpoint("/error_full_message_resources/new") do
|
14
|
+
get(HashDealer.roll(:new_error_resource))
|
15
|
+
end
|
16
|
+
|
17
|
+
endpoint("/error_full_message_resources") do
|
18
|
+
post(HashDealer.roll(:error_resource_full_message_errors), :params => {:error_full_message_resource => HashDealer.roll(:error_resource).matcher}, :status_code => 422)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
include ApiResource
|
2
|
+
|
3
|
+
Mocks.define do
|
4
|
+
|
5
|
+
endpoint("/test_resources/new") do
|
6
|
+
get(HashDealer.roll(:new_test_object))
|
7
|
+
end
|
8
|
+
|
9
|
+
endpoint("/test_resources") do
|
10
|
+
post(HashDealer.roll(:test_resource).merge(:id => 1), :params => {:test_resource => HashDealer.roll(:test_resource).matcher})
|
11
|
+
get((0..4).to_a.collect{HashDealer.roll(:test_resource)})
|
12
|
+
get((0..4).to_a.collect{HashDealer.roll(:test_resource)}, :params => {:active => true})
|
13
|
+
end
|
14
|
+
|
15
|
+
endpoint("/test_resources/:id") do
|
16
|
+
get(HashDealer.roll(:test_resource)) do |params|
|
17
|
+
self.merge(params)
|
18
|
+
end
|
19
|
+
delete({})
|
20
|
+
put({}, :params => {:test_resource => HashDealer.roll(:test_resource).matcher})
|
21
|
+
end
|
22
|
+
|
23
|
+
endpoint("/child_test_resources/new") do
|
24
|
+
get({})
|
25
|
+
end
|
26
|
+
|
27
|
+
endpoint("/child_test_resource2s/new") do
|
28
|
+
get({})
|
29
|
+
end
|
30
|
+
|
31
|
+
endpoint("/another_test_resources/new") do
|
32
|
+
get({})
|
33
|
+
end
|
34
|
+
|
35
|
+
endpoint("/test_classes/new") do
|
36
|
+
get({})
|
37
|
+
end
|
38
|
+
|
39
|
+
endpoint("/children/new") do
|
40
|
+
get({})
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
HashDealer.define(:test_association_resource) do
|
2
|
+
id{Kernel.rand(99999)}
|
3
|
+
name{Faker::Name.first_name}
|
4
|
+
age{Kernel.rand(99999)}
|
5
|
+
active(false)
|
6
|
+
end
|
7
|
+
|
8
|
+
HashDealer.define(:active_test_association_resource, :parent => :test_association_resource) do
|
9
|
+
active(true)
|
10
|
+
end
|
11
|
+
|
12
|
+
HashDealer.define(:active_birthday_test_association_resource, :parent => :active_test_association_resource) do
|
13
|
+
birthday{Date.today}
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
HashDealer.define(:new_error_resource) do
|
2
|
+
attributes({
|
3
|
+
:protected => [:id],
|
4
|
+
:public => [:name, :age],
|
5
|
+
})
|
6
|
+
end
|
7
|
+
|
8
|
+
HashDealer.define(:error_resource) do
|
9
|
+
name("Name")
|
10
|
+
age("age")
|
11
|
+
end
|
12
|
+
|
13
|
+
HashDealer.define(:error_resource_errors) do
|
14
|
+
errors({
|
15
|
+
:name => ["must not be empty"],
|
16
|
+
:age => ["must be a valid integer"]
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
HashDealer.define(:error_resource_full_message_errors) do
|
21
|
+
errors([
|
22
|
+
"Name cannot be empty",
|
23
|
+
"Age must be a valid integer"
|
24
|
+
])
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
HashDealer.define(:new_test_object) do
|
2
|
+
attributes({
|
3
|
+
:protected => [:id],
|
4
|
+
:public => [:name, :age],
|
5
|
+
})
|
6
|
+
scopes({
|
7
|
+
:active => {:active => true},
|
8
|
+
:paginate => {:paginate => true, :per_page => :per_page, :current_page => :current_page}
|
9
|
+
})
|
10
|
+
associations({
|
11
|
+
:has_many => {:has_many_objects => {}},
|
12
|
+
:belongs_to => {:belongs_to_object => {}, :custom_name => {:class_name => "BelongsToObject"}},
|
13
|
+
:has_one => {:has_one_object => {}},
|
14
|
+
})
|
15
|
+
# Think of a use case for this
|
16
|
+
options({
|
17
|
+
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
HashDealer.define(:test_resource) do
|
22
|
+
name("name")
|
23
|
+
age("age")
|
24
|
+
end
|
25
|
+
|
26
|
+
HashDealer.define(:test_resource_errors) do
|
27
|
+
errors({
|
28
|
+
:name => ["can't be blank"],
|
29
|
+
:age => ["must be a valid number"]
|
30
|
+
})
|
31
|
+
end
|