active_remote 1.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 (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