active_remote 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README.md +86 -0
- data/Rakefile +21 -0
- data/active_remote.gemspec +35 -0
- data/lib/active_remote.rb +15 -0
- data/lib/active_remote/association.rb +152 -0
- data/lib/active_remote/attributes.rb +29 -0
- data/lib/active_remote/base.rb +49 -0
- data/lib/active_remote/bulk.rb +143 -0
- data/lib/active_remote/dirty.rb +70 -0
- data/lib/active_remote/dsl.rb +141 -0
- data/lib/active_remote/errors.rb +24 -0
- data/lib/active_remote/persistence.rb +226 -0
- data/lib/active_remote/rpc.rb +71 -0
- data/lib/active_remote/search.rb +131 -0
- data/lib/active_remote/serialization.rb +40 -0
- data/lib/active_remote/serializers/json.rb +18 -0
- data/lib/active_remote/serializers/protobuf.rb +100 -0
- data/lib/active_remote/version.rb +3 -0
- data/lib/core_ext/date.rb +7 -0
- data/lib/core_ext/date_time.rb +7 -0
- data/lib/core_ext/integer.rb +19 -0
- data/lib/protobuf_extensions/base_field.rb +18 -0
- data/spec/core_ext/date_time_spec.rb +10 -0
- data/spec/lib/active_remote/association_spec.rb +80 -0
- data/spec/lib/active_remote/base_spec.rb +10 -0
- data/spec/lib/active_remote/bulk_spec.rb +74 -0
- data/spec/lib/active_remote/dsl_spec.rb +73 -0
- data/spec/lib/active_remote/persistence_spec.rb +266 -0
- data/spec/lib/active_remote/rpc_spec.rb +94 -0
- data/spec/lib/active_remote/search_spec.rb +98 -0
- data/spec/lib/active_remote/serialization_spec.rb +57 -0
- data/spec/lib/active_remote/serializers/json_spec.rb +32 -0
- data/spec/lib/active_remote/serializers/protobuf_spec.rb +95 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/definitions/author.proto +29 -0
- data/spec/support/definitions/post.proto +33 -0
- data/spec/support/definitions/support/protobuf/category.proto +29 -0
- data/spec/support/definitions/support/protobuf/error.proto +6 -0
- data/spec/support/definitions/tag.proto +29 -0
- data/spec/support/helpers.rb +37 -0
- data/spec/support/models.rb +5 -0
- data/spec/support/models/author.rb +14 -0
- data/spec/support/models/category.rb +14 -0
- data/spec/support/models/message_with_options.rb +11 -0
- data/spec/support/models/post.rb +16 -0
- data/spec/support/models/tag.rb +12 -0
- data/spec/support/protobuf.rb +4 -0
- data/spec/support/protobuf/author.pb.rb +54 -0
- data/spec/support/protobuf/category.pb.rb +54 -0
- data/spec/support/protobuf/error.pb.rb +21 -0
- data/spec/support/protobuf/post.pb.rb +58 -0
- data/spec/support/protobuf/tag.pb.rb +54 -0
- 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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|