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.
Files changed (59) hide show
  1. data/.document +5 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +29 -0
  4. data/Gemfile.lock +152 -0
  5. data/Guardfile +22 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +19 -0
  8. data/Rakefile +49 -0
  9. data/VERSION +1 -0
  10. data/api_resource.gemspec +154 -0
  11. data/lib/api_resource.rb +129 -0
  12. data/lib/api_resource/association_activation.rb +19 -0
  13. data/lib/api_resource/associations.rb +169 -0
  14. data/lib/api_resource/associations/association_proxy.rb +115 -0
  15. data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +16 -0
  16. data/lib/api_resource/associations/dynamic_resource_scope.rb +23 -0
  17. data/lib/api_resource/associations/has_many_remote_object_proxy.rb +16 -0
  18. data/lib/api_resource/associations/has_one_remote_object_proxy.rb +24 -0
  19. data/lib/api_resource/associations/multi_argument_resource_scope.rb +15 -0
  20. data/lib/api_resource/associations/multi_object_proxy.rb +73 -0
  21. data/lib/api_resource/associations/related_object_hash.rb +12 -0
  22. data/lib/api_resource/associations/relation_scope.rb +30 -0
  23. data/lib/api_resource/associations/resource_scope.rb +34 -0
  24. data/lib/api_resource/associations/scope.rb +107 -0
  25. data/lib/api_resource/associations/single_object_proxy.rb +81 -0
  26. data/lib/api_resource/attributes.rb +162 -0
  27. data/lib/api_resource/base.rb +587 -0
  28. data/lib/api_resource/callbacks.rb +49 -0
  29. data/lib/api_resource/connection.rb +171 -0
  30. data/lib/api_resource/core_extensions.rb +7 -0
  31. data/lib/api_resource/custom_methods.rb +119 -0
  32. data/lib/api_resource/exceptions.rb +87 -0
  33. data/lib/api_resource/formats.rb +14 -0
  34. data/lib/api_resource/formats/json_format.rb +25 -0
  35. data/lib/api_resource/formats/xml_format.rb +36 -0
  36. data/lib/api_resource/local.rb +12 -0
  37. data/lib/api_resource/log_subscriber.rb +15 -0
  38. data/lib/api_resource/mocks.rb +269 -0
  39. data/lib/api_resource/model_errors.rb +86 -0
  40. data/lib/api_resource/observing.rb +29 -0
  41. data/lib/api_resource/railtie.rb +22 -0
  42. data/lib/api_resource/scopes.rb +45 -0
  43. data/spec/lib/associations_spec.rb +656 -0
  44. data/spec/lib/attributes_spec.rb +121 -0
  45. data/spec/lib/base_spec.rb +504 -0
  46. data/spec/lib/callbacks_spec.rb +68 -0
  47. data/spec/lib/connection_spec.rb +76 -0
  48. data/spec/lib/local_spec.rb +20 -0
  49. data/spec/lib/mocks_spec.rb +28 -0
  50. data/spec/lib/model_errors_spec.rb +29 -0
  51. data/spec/spec_helper.rb +36 -0
  52. data/spec/support/mocks/association_mocks.rb +46 -0
  53. data/spec/support/mocks/error_resource_mocks.rb +21 -0
  54. data/spec/support/mocks/test_resource_mocks.rb +43 -0
  55. data/spec/support/requests/association_requests.rb +14 -0
  56. data/spec/support/requests/error_resource_requests.rb +25 -0
  57. data/spec/support/requests/test_resource_requests.rb +31 -0
  58. data/spec/support/test_resource.rb +64 -0
  59. 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
@@ -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