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.
- 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
|