active_remote 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +10 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +22 -0
  6. data/README.md +86 -0
  7. data/Rakefile +21 -0
  8. data/active_remote.gemspec +35 -0
  9. data/lib/active_remote.rb +15 -0
  10. data/lib/active_remote/association.rb +152 -0
  11. data/lib/active_remote/attributes.rb +29 -0
  12. data/lib/active_remote/base.rb +49 -0
  13. data/lib/active_remote/bulk.rb +143 -0
  14. data/lib/active_remote/dirty.rb +70 -0
  15. data/lib/active_remote/dsl.rb +141 -0
  16. data/lib/active_remote/errors.rb +24 -0
  17. data/lib/active_remote/persistence.rb +226 -0
  18. data/lib/active_remote/rpc.rb +71 -0
  19. data/lib/active_remote/search.rb +131 -0
  20. data/lib/active_remote/serialization.rb +40 -0
  21. data/lib/active_remote/serializers/json.rb +18 -0
  22. data/lib/active_remote/serializers/protobuf.rb +100 -0
  23. data/lib/active_remote/version.rb +3 -0
  24. data/lib/core_ext/date.rb +7 -0
  25. data/lib/core_ext/date_time.rb +7 -0
  26. data/lib/core_ext/integer.rb +19 -0
  27. data/lib/protobuf_extensions/base_field.rb +18 -0
  28. data/spec/core_ext/date_time_spec.rb +10 -0
  29. data/spec/lib/active_remote/association_spec.rb +80 -0
  30. data/spec/lib/active_remote/base_spec.rb +10 -0
  31. data/spec/lib/active_remote/bulk_spec.rb +74 -0
  32. data/spec/lib/active_remote/dsl_spec.rb +73 -0
  33. data/spec/lib/active_remote/persistence_spec.rb +266 -0
  34. data/spec/lib/active_remote/rpc_spec.rb +94 -0
  35. data/spec/lib/active_remote/search_spec.rb +98 -0
  36. data/spec/lib/active_remote/serialization_spec.rb +57 -0
  37. data/spec/lib/active_remote/serializers/json_spec.rb +32 -0
  38. data/spec/lib/active_remote/serializers/protobuf_spec.rb +95 -0
  39. data/spec/spec_helper.rb +17 -0
  40. data/spec/support/definitions/author.proto +29 -0
  41. data/spec/support/definitions/post.proto +33 -0
  42. data/spec/support/definitions/support/protobuf/category.proto +29 -0
  43. data/spec/support/definitions/support/protobuf/error.proto +6 -0
  44. data/spec/support/definitions/tag.proto +29 -0
  45. data/spec/support/helpers.rb +37 -0
  46. data/spec/support/models.rb +5 -0
  47. data/spec/support/models/author.rb +14 -0
  48. data/spec/support/models/category.rb +14 -0
  49. data/spec/support/models/message_with_options.rb +11 -0
  50. data/spec/support/models/post.rb +16 -0
  51. data/spec/support/models/tag.rb +12 -0
  52. data/spec/support/protobuf.rb +4 -0
  53. data/spec/support/protobuf/author.pb.rb +54 -0
  54. data/spec/support/protobuf/category.pb.rb +54 -0
  55. data/spec/support/protobuf/error.pb.rb +21 -0
  56. data/spec/support/protobuf/post.pb.rb +58 -0
  57. data/spec/support/protobuf/tag.pb.rb +54 -0
  58. metadata +284 -0
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRemote::Search do
4
+ describe ".find" do
5
+ let(:args) { Hash.new }
6
+ let(:record) { double(:record) }
7
+ let(:records) { [ record ] }
8
+
9
+ before { Tag.stub(:search).and_return(records) }
10
+
11
+ it "searches with the given args" do
12
+ Tag.should_receive(:search).with(args)
13
+ Tag.find(args)
14
+ end
15
+
16
+ context "when records are returned" do
17
+ it "returns the first record" do
18
+ Tag.find(args).should eq record
19
+ end
20
+ end
21
+
22
+ context "when no records are returned" do
23
+ before { Tag.stub(:search).and_return([]) }
24
+
25
+ it "raise an exception" do
26
+ expect { Tag.find(args) }.to raise_error(::ActiveRemote::RemoteRecordNotFound)
27
+ end
28
+ end
29
+ end
30
+
31
+ describe ".search" do
32
+ context "given args that respond to :to_hash" do
33
+ let(:args) { Hash.new }
34
+
35
+ before {
36
+ Tag.any_instance.stub(:_active_remote_search)
37
+ }
38
+
39
+ it "searches with the given args" do
40
+ Tag.any_instance.should_receive(:_active_remote_search).with(args)
41
+ Tag.search(args)
42
+ end
43
+
44
+ it "returns records" do
45
+ records = double(:records)
46
+
47
+ Tag.any_instance.stub(:serialize_records).and_return(records)
48
+ Tag.search(args).should eq records
49
+ end
50
+ end
51
+
52
+ context "given args that don't respond to :to_hash" do
53
+ let(:request) { double(:request) }
54
+
55
+ it "raises an exception" do
56
+ expect { described_class.search(request) }.to raise_exception
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "#_active_remote_search" do
62
+ let(:args) { Hash.new }
63
+
64
+ subject { Tag.new }
65
+
66
+ it "runs callbacks" do
67
+ subject.should_receive(:run_callbacks).with(:search)
68
+ subject._active_remote_search(args)
69
+ end
70
+
71
+ it "executes the search" do
72
+ subject.should_receive(:execute).with(:search, args)
73
+ subject._active_remote_search(args)
74
+ end
75
+ end
76
+
77
+ describe "#reload" do
78
+ let(:args) { { :guid => 'foo' } }
79
+ let(:attributes) { HashWithIndifferentAccess.new }
80
+
81
+ subject { Tag.new(args) }
82
+
83
+ before {
84
+ subject.stub(:_active_remote_search)
85
+ subject.stub(:last_response).and_return(attributes)
86
+ }
87
+
88
+ it "reloads the record" do
89
+ subject.should_receive(:_active_remote_search).with(args)
90
+ subject.reload
91
+ end
92
+
93
+ it "assigns new attributes" do
94
+ subject.should_receive(:assign_attributes).with(attributes)
95
+ subject.reload
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRemote::Serialization do
4
+ describe "#add_errors_from_response" do
5
+ let(:error) { Generic::Error.new(:field => 'name', :message => 'Boom!') }
6
+ let(:last_response) {
7
+ tag = Generic::Remote::Tag.new
8
+ tag.errors << error
9
+ tag
10
+ }
11
+
12
+ subject { Tag.new }
13
+
14
+ context "when the last response has errors" do
15
+
16
+ before { subject.stub(:last_response).and_return(last_response) }
17
+
18
+ it "adds the errors to the active remote object" do
19
+ subject.add_errors_from_response
20
+ subject.errors[:name].should =~ ['Boom!']
21
+ end
22
+ end
23
+
24
+ context "when the last response doesn't respond to errors" do
25
+ it "doesn't add errors" do
26
+ subject.add_errors_from_response
27
+ subject.errors.empty?.should be_true
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "#serialize_records" do
33
+ let(:last_response) {
34
+ MessageWithOptions.new(:records => records)
35
+ }
36
+ let(:records) { [ { :foo => 'bar' } ] }
37
+
38
+ subject { Tag.new }
39
+
40
+ context "when the last response has records" do
41
+
42
+ before { subject.stub(:last_response).and_return(last_response) }
43
+
44
+ it "serializes records into active remote objects" do
45
+ subject.serialize_records.each do |record|
46
+ record.should be_a Tag
47
+ end
48
+ end
49
+ end
50
+
51
+ context "when the last response doesn't respond to records" do
52
+ it "returns nil" do
53
+ subject.serialize_records.should be_nil
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRemote::Serializers::JSON do
4
+ describe "#as_json" do
5
+ let(:attributes) { { :guid => 'foo', :name => 'bar' } }
6
+
7
+ subject { Tag.new(attributes) }
8
+
9
+ it "accepts standard JSON options" do
10
+ subject.as_json(:root => false).should eq attributes.stringify_keys
11
+ end
12
+
13
+ context "with publishable attributes defined" do
14
+ let(:expected_json) { { :tag => attributes.slice(:name) }.to_json }
15
+
16
+ before { Tag.attr_publishable :name }
17
+ after { reset_publishable_attributes(Tag) }
18
+
19
+ it "serializes to JSON with only the publishable attributes" do
20
+ subject.to_json.should eq expected_json
21
+ end
22
+ end
23
+
24
+ context "without publishable attributes defined" do
25
+ let(:expected_json) { { :tag => attributes }.to_json }
26
+
27
+ it "serializes to JSON" do
28
+ subject.to_json.should eq expected_json
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRemote::Serializers::Protobuf do
4
+ describe ".build_message" do
5
+ it "coerces the attribute value to a compatible type"
6
+
7
+ it "builds a protobuf message"
8
+
9
+ context "when the message doesn't have a field matching a given attribute" do
10
+ it "skips the attribute"
11
+ end
12
+
13
+ context "when a field is repeated" do
14
+ it "converts the attribute to a collection"
15
+ it "coerces the attribute value(s) to a compatible type"
16
+ end
17
+
18
+ context "when the field is an enum" do
19
+ it "converts the attribute into an integer"
20
+ end
21
+
22
+ context "when a field is a message" do
23
+ let(:attributes) { { :category => category } }
24
+ let(:category) { { :name => 'foo' } }
25
+ let(:category_message) { Generic::Remote::Category.new(category) }
26
+
27
+ context "and the value is a hash" do
28
+ it "builds new messages with the value(s)" do
29
+ message = ::ActiveRemote::Base.build_message(Generic::Remote::Post, attributes)
30
+ message.category.should eq (category_message)
31
+ end
32
+ end
33
+
34
+ context "and the value is a message" do
35
+ let(:attributes) { { :category => category_message } }
36
+
37
+ it "returns the value" do
38
+ message = ::ActiveRemote::Base.build_message(Generic::Remote::Post, attributes)
39
+ message.category.should eq (category_message)
40
+ end
41
+ end
42
+
43
+ context "and the field is repeated" do
44
+ context "and the value is a hash" do
45
+ let(:attributes) { { :records => [ tag ] } }
46
+ let(:tag) { { :name => 'foo' } }
47
+ let(:tag_message) { Generic::Remote::Tag.new(tag) }
48
+
49
+ it "builds new messages with the value(s)" do
50
+ message = ::ActiveRemote::Base.build_message(Generic::Remote::Tags, attributes)
51
+ message.records.first.should eq (tag_message)
52
+ end
53
+ end
54
+
55
+ context "and the value is a message" do
56
+ let(:attributes) { { :records => [ tag_message ] } }
57
+ let(:tag_message) { Generic::Remote::Tag.new }
58
+
59
+ it "returns the value" do
60
+ message = ::ActiveRemote::Base.build_message(Generic::Remote::Tags, attributes)
61
+ message.records.first.should eq (tag_message)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ describe ".coerce" do
69
+ context "when field_type is :bool" do
70
+ context "and value is 1" do
71
+ it "returns true"
72
+ end
73
+
74
+ context "and value is 0" do
75
+ it "returns false"
76
+ end
77
+ end
78
+
79
+ context "when the field_type is :int32" do
80
+ it "returns an integer"
81
+ end
82
+
83
+ context "when the field_type is :int64" do
84
+ it "returns an integer"
85
+ end
86
+
87
+ context "when the field_type is :double" do
88
+ it "returns a float"
89
+ end
90
+
91
+ context "when the field_type is :string" do
92
+ it "returns a string"
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ require 'simplecov'
5
+ SimpleCov.start do
6
+ add_filter '/spec/'
7
+ end
8
+
9
+ Bundler.require(:default, :development, :test)
10
+
11
+ require 'protobuf/rspec'
12
+ require 'support/helpers'
13
+ require 'support/protobuf'
14
+
15
+ RSpec.configure do |config|
16
+ config.include Protobuf::RSpec::Helpers
17
+ end
@@ -0,0 +1,29 @@
1
+ package generic.remote;
2
+
3
+ import "support/protobuf/error.proto";
4
+
5
+ message Author {
6
+ optional string guid = 1;
7
+ optional string name = 2;
8
+ repeated Error errors = 3;
9
+ }
10
+
11
+ message Authors {
12
+ repeated Author records = 1;
13
+ }
14
+
15
+ message AuthorRequest {
16
+ repeated string guid = 1;
17
+ repeated string name = 2;
18
+ }
19
+
20
+ service AuthorService {
21
+ rpc Search (AuthorRequest) returns (Authors);
22
+ rpc Create (Author) returns (Author);
23
+ rpc Update (Author) returns (Author);
24
+ rpc Delete (Author) returns (Author);
25
+ rpc CreateAll (Authors) returns (Authors);
26
+ rpc UpdateAll (Authors) returns (Authors);
27
+ rpc DeleteAll (Authors) returns (Authors);
28
+ rpc DestroyAll (Authors) returns (Authors);
29
+ }
@@ -0,0 +1,33 @@
1
+ package generic.remote;
2
+
3
+ import "support/protobuf/error.proto";
4
+ import "support/protobuf/category.proto";
5
+
6
+ message Post {
7
+ optional string guid = 1;
8
+ optional string name = 2;
9
+ optional string author_guid = 3;
10
+ optional Category category = 4;
11
+ repeated Error errors = 5;
12
+ }
13
+
14
+ message Posts {
15
+ repeated Post records = 1;
16
+ }
17
+
18
+ message PostRequest {
19
+ repeated string guid = 1;
20
+ repeated string name = 2;
21
+ repeated string author_guid = 3;
22
+ }
23
+
24
+ service PostService {
25
+ rpc Search (PostRequest) returns (Posts);
26
+ rpc Create (Post) returns (Post);
27
+ rpc Update (Post) returns (Post);
28
+ rpc Delete (Post) returns (Post);
29
+ rpc CreateAll (Posts) returns (Posts);
30
+ rpc UpdateAll (Posts) returns (Posts);
31
+ rpc DeleteAll (Posts) returns (Posts);
32
+ rpc DestroyAll (Posts) returns (Posts);
33
+ }
@@ -0,0 +1,29 @@
1
+ package generic.remote;
2
+
3
+ import "support/protobuf/error.proto";
4
+
5
+ message Category {
6
+ optional string guid = 1;
7
+ optional string name = 2;
8
+ repeated Error errors = 3;
9
+ }
10
+
11
+ message Categories {
12
+ repeated Category records = 1;
13
+ }
14
+
15
+ message CategoryRequest {
16
+ repeated string guid = 1;
17
+ repeated string name = 2;
18
+ }
19
+
20
+ service CategoryService {
21
+ rpc Search (CategoryRequest) returns (Categories);
22
+ rpc Create (Category) returns (Category);
23
+ rpc Update (Category) returns (Category);
24
+ rpc Delete (Category) returns (Category);
25
+ rpc CreateAll (Categories) returns (Categories);
26
+ rpc UpdateAll (Categories) returns (Categories);
27
+ rpc DeleteAll (Categories) returns (Categories);
28
+ rpc DestroyAll (Categories) returns (Categories);
29
+ }
@@ -0,0 +1,6 @@
1
+ package generic;
2
+
3
+ message Error {
4
+ optional string field = 1;
5
+ optional string message = 2;
6
+ }
@@ -0,0 +1,29 @@
1
+ package generic.remote;
2
+
3
+ import "support/protobuf/error.proto";
4
+
5
+ message Tag {
6
+ optional string guid = 1;
7
+ optional string name = 2;
8
+ repeated Error errors = 3;
9
+ }
10
+
11
+ message Tags {
12
+ repeated Tag records = 1;
13
+ }
14
+
15
+ message TagRequest {
16
+ repeated string guid = 1;
17
+ repeated string name = 2;
18
+ }
19
+
20
+ service TagService {
21
+ rpc Search (TagRequest) returns (Tags);
22
+ rpc Create (Tag) returns (Tag);
23
+ rpc Update (Tag) returns (Tag);
24
+ rpc Delete (Tag) returns (Tag);
25
+ rpc CreateAll (Tags) returns (Tags);
26
+ rpc UpdateAll (Tags) returns (Tags);
27
+ rpc DeleteAll (Tags) returns (Tags);
28
+ rpc DestroyAll (Tags) returns (Tags);
29
+ }
@@ -0,0 +1,37 @@
1
+ require 'support/models'
2
+
3
+ ##
4
+ # Reset all DSL variables so specs don't interfere with each other.
5
+ #
6
+ def reset_dsl_variables(klass)
7
+ reset_app_name(klass)
8
+ reset_auto_paging_size(klass)
9
+ reset_namespace(klass)
10
+ reset_publishable_attributes(klass)
11
+ reset_service_class(klass)
12
+ reset_service_name(klass)
13
+ end
14
+
15
+ def reset_app_name(klass)
16
+ klass.send(:instance_variable_set, :@app_name, nil)
17
+ end
18
+
19
+ def reset_auto_paging_size(klass)
20
+ klass.send(:instance_variable_set, :@auto_paging_size, nil)
21
+ end
22
+
23
+ def reset_namespace(klass)
24
+ klass.send(:instance_variable_set, :@namespace, nil)
25
+ end
26
+
27
+ def reset_publishable_attributes(klass)
28
+ klass.send(:instance_variable_set, :@publishable_attributes, nil)
29
+ end
30
+
31
+ def reset_service_class(klass)
32
+ klass.send(:instance_variable_set, :@service_class, nil)
33
+ end
34
+
35
+ def reset_service_name(klass)
36
+ klass.send(:instance_variable_set, :@service_name, nil)
37
+ end