active_remote 2.4.0 → 3.0.0.pre1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -0
  3. data/CHANGES.md +22 -0
  4. data/README.md +2 -0
  5. data/active_remote.gemspec +2 -2
  6. data/bin/console +10 -0
  7. data/lib/active_remote/association.rb +4 -0
  8. data/lib/active_remote/attribute_assignment.rb +53 -0
  9. data/lib/active_remote/attribute_definition.rb +106 -0
  10. data/lib/active_remote/attributes.rb +165 -8
  11. data/lib/active_remote/base.rb +41 -36
  12. data/lib/active_remote/dirty.rb +0 -9
  13. data/lib/active_remote/errors.rb +6 -0
  14. data/lib/active_remote/persistence.rb +10 -19
  15. data/lib/active_remote/query_attributes.rb +45 -0
  16. data/lib/active_remote/rpc.rb +17 -67
  17. data/lib/active_remote/search.rb +0 -20
  18. data/lib/active_remote/serialization.rb +1 -32
  19. data/lib/active_remote/typecasting.rb +3 -12
  20. data/lib/active_remote/version.rb +1 -1
  21. data/lib/active_remote.rb +0 -2
  22. data/spec/lib/active_remote/association_spec.rb +11 -2
  23. data/spec/lib/active_remote/attributes_spec.rb +177 -0
  24. data/spec/lib/active_remote/persistence_spec.rb +7 -16
  25. data/spec/lib/active_remote/query_attribute_spec.rb +171 -0
  26. data/spec/lib/active_remote/rpc_spec.rb +33 -75
  27. data/spec/lib/active_remote/search_spec.rb +0 -21
  28. data/spec/lib/active_remote/serialization_spec.rb +0 -23
  29. data/spec/support/models/no_attributes.rb +2 -0
  30. data/spec/support/models.rb +1 -0
  31. metadata +21 -66
  32. data/lib/active_remote/attribute_defaults.rb +0 -100
  33. data/lib/active_remote/bulk.rb +0 -168
  34. data/lib/active_remote/core_ext/date.rb +0 -7
  35. data/lib/active_remote/core_ext/date_time.rb +0 -7
  36. data/lib/active_remote/core_ext/integer.rb +0 -19
  37. data/lib/active_remote/core_ext.rb +0 -3
  38. data/lib/active_remote/publication.rb +0 -54
  39. data/lib/active_remote/serializers/json.rb +0 -16
  40. data/spec/core_ext/date_time_spec.rb +0 -9
  41. data/spec/lib/active_remote/attribute_defaults_spec.rb +0 -26
  42. data/spec/lib/active_remote/bulk_spec.rb +0 -83
  43. data/spec/lib/active_remote/publication_spec.rb +0 -18
  44. data/spec/lib/active_remote/serializers/json_spec.rb +0 -78
@@ -0,0 +1,45 @@
1
+ require "active_support/concern"
2
+ require "active_support/core_ext/object/blank"
3
+
4
+ module ActiveRemote
5
+ # QueryAttributes provides instance methods for querying attributes.
6
+ #
7
+ # @example Usage
8
+ # class Person < ::ActiveRemote::Base
9
+ # attribute :name
10
+ # end
11
+ #
12
+ # person = Person.new
13
+ # person.name? #=> false
14
+ # person.name = "Chris Griego"
15
+ # person.name? #=> true
16
+ #
17
+ module QueryAttributes
18
+ extend ::ActiveSupport::Concern
19
+
20
+ included do
21
+ attribute_method_suffix "?"
22
+ end
23
+
24
+ # Test the presence of an attribute
25
+ #
26
+ # See {Typecasting::BooleanTypecaster.call} for more details.
27
+ #
28
+ # @example Query an attribute
29
+ # person.query_attribute(:name)
30
+ #
31
+ def query_attribute(name)
32
+ if respond_to?("#{name}?")
33
+ send("#{name}?")
34
+ else
35
+ raise ::ActiveRemote::UnknownAttributeError, "unknown attribute: #{name}"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def attribute?(name)
42
+ ::ActiveRemote::Typecasting::BooleanTypecaster.call(read_attribute(name))
43
+ end
44
+ end
45
+ end
@@ -3,80 +3,30 @@ require 'active_remote/serializers/protobuf'
3
3
 
4
4
  module ActiveRemote
5
5
  module RPC
6
- extend ActiveSupport::Concern
6
+ extend ::ActiveSupport::Concern
7
7
 
8
8
  included do
9
- include Embedded
9
+ include ::ActiveRemote::Serializers::Protobuf
10
10
  end
11
11
 
12
- module Embedded
13
- extend ActiveSupport::Concern
14
-
15
- included do
16
- include Serializers::Protobuf
17
- end
18
-
19
- module ClassMethods
20
- # :noapi:
21
- def request(rpc_method, request_args)
22
- warn "DEPRECATED Model.request is deprecated and will be removed in Active Remote 3.0. This is handled by the Protobuf RPC adpater now"
23
-
24
- return request_args unless request_args.is_a?(Hash)
25
-
26
- message_class = request_type(rpc_method)
27
- fields = fields_from_attributes(message_class, request_args)
28
- message_class.new(fields)
29
- end
30
-
31
- # :noapi:
32
- def request_type(rpc_method)
33
- warn "DEPRECATED Model.request_type is deprecated and will be removed in Active Remote 3.0. This is handled by the Protobuf RPC adpater now"
34
-
35
- service_class.rpcs[rpc_method].request_type
36
- end
37
- end
38
-
39
- # :noapi:
40
- def execute(rpc_method, request_args)
41
- warn "DEPRECATED Model#execute is deprecated and will be removed in Active Remote 3.0. Use Model#rpc.execute instead"
42
-
43
- @last_request = request(rpc_method, request_args)
44
-
45
- _service_class.client.__send__(rpc_method, @last_request) do |c|
46
-
47
- # In the event of service failure, raise the error.
48
- c.on_failure do |error|
49
- raise ActiveRemoteError, error.message
50
- end
51
-
52
- # In the event of service success, assign the response.
53
- c.on_success do |response|
54
- @last_response = response
12
+ module ClassMethods
13
+ # Builds an attribute hash that be assigned directly
14
+ # to an object from an rpc response
15
+ def build_from_rpc(new_attributes)
16
+ new_attributes = new_attributes.stringify_keys
17
+ constructed_attributes = {}
18
+ attributes.each do |name, definition|
19
+ if new_attributes[name].nil?
20
+ constructed_attributes[name] = nil
21
+ elsif definition[:typecaster]
22
+ constructed_attributes[name] = definition[:typecaster].call(new_attributes[name])
23
+ else
24
+ constructed_attributes[name] = new_attributes[name]
55
25
  end
56
26
  end
57
-
58
- @last_response
59
- end
60
-
61
- # :noapi:
62
- def remote_call(rpc_method, request_args)
63
- warn "DEPRECATED Model#remote_call is deprecated and will be removed in Active Remote 3.0. Use Model#rpc.execute instead"
64
-
65
- rpc.execute(rpc_method, request_args)
27
+ constructed_attributes
66
28
  end
67
29
 
68
- private
69
-
70
- # :noapi:
71
- def request(rpc_method, attributes)
72
- warn "DEPRECATED Model#request is deprecated and will be removed in Active Remote 3.0. This is handled by the Protobuf RPC adpater now"
73
-
74
- self.class.request(rpc_method, attributes)
75
- end
76
- end
77
-
78
- module ClassMethods
79
-
80
30
  # Execute an RPC call to the remote service and return the raw response.
81
31
  #
82
32
  def remote_call(rpc_method, request_args)
@@ -104,7 +54,7 @@ module ActiveRemote
104
54
  #
105
55
  # path_to_adapter.classify.constantize
106
56
 
107
- RPCAdapters::ProtobufAdapter
57
+ ::ActiveRemote::RPCAdapters::ProtobufAdapter
108
58
  end
109
59
  end
110
60
 
@@ -8,19 +8,9 @@ module ActiveRemote
8
8
  included do
9
9
  include Persistence
10
10
  include RPC
11
-
12
- define_model_callbacks :search
13
11
  end
14
12
 
15
13
  module ClassMethods
16
-
17
- # :noapi:
18
- def _active_remote_search_args(args)
19
- warn "DEPRECATED Model._active_remote_search_args is depracted and will be remoted in Active Remote 3.0."
20
-
21
- validate_search_args!(args)
22
- end
23
-
24
14
  # Tries to load the first record; if it fails, an exception is raised.
25
15
  #
26
16
  # ====Examples
@@ -102,7 +92,6 @@ module ActiveRemote
102
92
 
103
93
  if response.respond_to?(:records)
104
94
  records = serialize_records(response.records)
105
- records.each { |record| record.run_callbacks :search }
106
95
  end
107
96
  end
108
97
 
@@ -122,15 +111,6 @@ module ActiveRemote
122
111
  end
123
112
  end
124
113
 
125
- # :noapi:
126
- def _active_remote_search(args)
127
- warn "DEPRECATED Model#_active_remote_search is depracted and will be remoted in Active Remote 3.0."
128
-
129
- run_callbacks :search do
130
- rpc.execute(:search, args)
131
- end
132
- end
133
-
134
114
  # Reload this record from the remote service.
135
115
  #
136
116
  def reload
@@ -1,11 +1,9 @@
1
- require 'active_remote/serializers/json'
2
-
3
1
  module ActiveRemote
4
2
  module Serialization
5
3
  extend ActiveSupport::Concern
6
4
 
7
5
  included do
8
- include Serializers::JSON
6
+ include ::ActiveModel::Serializers::JSON
9
7
  end
10
8
 
11
9
  module ClassMethods
@@ -45,34 +43,5 @@ module ActiveRemote
45
43
  end
46
44
  end
47
45
  end
48
-
49
- # Examine the given response and add any errors to our internal errors
50
- # list.
51
- #
52
- # ====Examples
53
- #
54
- # response = remote_call(:action_that_returns_errors, { :stuff => 'foo' })
55
- #
56
- # add_errors_from_response(response)
57
- #
58
- def add_errors_from_response(response = nil)
59
- unless response
60
- warn 'DEPRECATED calling Model#add_errors_from_response without args is deprecated and will be removed in Active Remote 3.0.'
61
- response = last_response
62
- end
63
-
64
- add_errors(response.errors) if response.respond_to?(:errors)
65
- end
66
-
67
- # DEPRECATED – Use the class-level :serialize_errors instead
68
- #
69
- def serialize_records(records = nil)
70
- warn 'DEPRECATED Calling Model#serialize_records is deprecated and will be removed in Active Remote 3.0. Use Model.serialize_records instead'
71
-
72
- records ||= last_response.records if last_response.respond_to?(:records)
73
- return if records.nil?
74
-
75
- self.class.serialize_records(records)
76
- end
77
46
  end
78
47
  end
@@ -1,3 +1,5 @@
1
+ require "active_support/core_ext/string/conversions"
2
+
1
3
  require "active_remote/typecasting/big_decimal_typecaster"
2
4
  require "active_remote/typecasting/boolean"
3
5
  require "active_remote/typecasting/boolean_typecaster"
@@ -28,23 +30,12 @@ module ActiveRemote
28
30
  def attribute=(name, value)
29
31
  return super if value.nil?
30
32
 
31
- typecaster = _attribute_typecaster(name)
33
+ typecaster = self.class.attributes[name][:typecaster]
32
34
  return super unless typecaster
33
35
 
34
36
  super(name, typecaster.call(value))
35
37
  end
36
38
 
37
- def _attribute_typecaster(attribute_name)
38
- self.class.attributes[attribute_name][:typecaster] || _typecaster_for(attribute_name)
39
- end
40
-
41
- def _typecaster_for(attribute_name)
42
- type = self.class.attributes[attribute_name][:type]
43
- return nil unless type
44
-
45
- TYPECASTER_MAP[type]
46
- end
47
-
48
39
  module ClassMethods
49
40
  def inspect
50
41
  inspected_attributes = attribute_names.sort.map { |name| "#{name}: #{_attribute_type(name)}" }
@@ -1,3 +1,3 @@
1
1
  module ActiveRemote
2
- VERSION = "2.4.0"
2
+ VERSION = "3.0.0.pre1"
3
3
  end
data/lib/active_remote.rb CHANGED
@@ -1,11 +1,9 @@
1
- require 'active_attr'
2
1
  require 'active_model'
3
2
  require 'active_support'
4
3
  require 'protobuf'
5
4
 
6
5
  require 'active_remote/base'
7
6
  require 'active_remote/config'
8
- require 'active_remote/core_ext'
9
7
  require 'active_remote/errors'
10
8
  require 'active_remote/version'
11
9
 
@@ -102,6 +102,7 @@ describe ActiveRemote::Association do
102
102
  subject { Author.new(:guid => guid, :user_guid => user_guid) }
103
103
 
104
104
  it { is_expected.to respond_to(:posts) }
105
+ it { is_expected.to respond_to(:posts=) }
105
106
 
106
107
  it "searches the associated model for all associated records" do
107
108
  expect(Post).to receive(:search).with(:author_guid => subject.guid).and_return(records)
@@ -158,7 +159,7 @@ describe ActiveRemote::Association do
158
159
  before { allow(subject).to receive(:respond_to?).with("user_guid").and_return(false) }
159
160
 
160
161
  it 'raises an error' do
161
- expect {subject.user_posts}.to raise_error(::ActiveAttr::UnknownAttributeError)
162
+ expect {subject.user_posts}.to raise_error(::ActiveRemote::UnknownAttributeError)
162
163
  end
163
164
  end
164
165
 
@@ -170,6 +171,14 @@ describe ActiveRemote::Association do
170
171
  end
171
172
  end
172
173
  end
174
+
175
+ context "writer method" do
176
+ context "when new value is not an array" do
177
+ it "should raise error" do
178
+ expect { subject.posts = Post.new }.to raise_error(::RuntimeError, /New value must be an array/)
179
+ end
180
+ end
181
+ end
173
182
  end
174
183
 
175
184
  describe ".has_one" do
@@ -246,7 +255,7 @@ describe ActiveRemote::Association do
246
255
  before { allow(subject).to receive(:respond_to?).with("user_guid").and_return(false) }
247
256
 
248
257
  it 'raises an error' do
249
- expect {subject.chief_editor}.to raise_error(::ActiveAttr::UnknownAttributeError)
258
+ expect {subject.chief_editor}.to raise_error(::ActiveRemote::UnknownAttributeError)
250
259
  end
251
260
  end
252
261
 
@@ -0,0 +1,177 @@
1
+ require "spec_helper"
2
+
3
+ describe ::ActiveRemote::Attributes do
4
+ let(:model_class) do
5
+ ::Class.new do
6
+ include ::ActiveRemote::Attributes
7
+
8
+ attribute :name
9
+ attribute :address
10
+
11
+ def self.name
12
+ "TestClass"
13
+ end
14
+ end
15
+ end
16
+ subject { ::Author.new }
17
+
18
+ describe ".attribute" do
19
+ context "a dangerous attribute" do
20
+ it "raises an error" do
21
+ expect { model_class.attribute(:timeout) }.to raise_error(::ActiveRemote::DangerousAttributeError)
22
+ end
23
+ end
24
+
25
+ context "a harmless attribute" do
26
+ it "creates an attribute with no options" do
27
+ expect(model_class.attributes.values).to include(::ActiveRemote::AttributeDefinition.new(:name))
28
+ end
29
+
30
+ it "returns the attribute definition" do
31
+ expect(model_class.attribute(:name)).to eq(::ActiveRemote::AttributeDefinition.new(:name))
32
+ end
33
+
34
+ it "defines an attribute reader that calls #attribute" do
35
+ expect(subject).to receive(:attribute).with("name")
36
+ subject.name
37
+ end
38
+
39
+ it "defines an attribute writer that calls #attribute=" do
40
+ expect(subject).to receive(:attribute=).with("name", "test")
41
+ subject.name = "test"
42
+ end
43
+ end
44
+ end
45
+
46
+ describe ".attribute!" do
47
+ it "can create an attribute with no options" do
48
+ model_class.attribute!(:first_name)
49
+ expect(model_class.attributes.values).to include(::ActiveRemote::AttributeDefinition.new(:first_name))
50
+ end
51
+
52
+ it "returns the attribute definition" do
53
+ expect(model_class.attribute!(:address)).to eq(::ActiveRemote::AttributeDefinition.new(:address))
54
+ end
55
+ end
56
+
57
+ describe ".attributes" do
58
+ it "can access AttributeDefinition with a Symbol" do
59
+ expect(::Author.attributes[:name]).to eq(::ActiveRemote::AttributeDefinition.new(:name))
60
+ end
61
+
62
+ it "can access AttributeDefinition with a String" do
63
+ expect(::Author.attributes["name"]).to eq(::ActiveRemote::AttributeDefinition.new(:name))
64
+ end
65
+ end
66
+
67
+ describe ".inspect" do
68
+ it "renders the class name" do
69
+ expect(model_class.inspect).to match(/^TestClass\(.*\)$/)
70
+ end
71
+
72
+ it "renders the attribute names in alphabetical order" do
73
+ expect(model_class.inspect).to match("(address, name)")
74
+ end
75
+ end
76
+
77
+ describe "#==" do
78
+ it "returns true when all attributes are equal" do
79
+ expect(::Author.new(:guid => "test")).to eq(::Author.new(:guid => "test"))
80
+ end
81
+
82
+ it "returns false when compared to another type" do
83
+ expect(::Category.new(:guid => "test")).to_not eq(::Author.new(:name => "test"))
84
+ end
85
+ end
86
+
87
+ describe "#attributes" do
88
+ context "when no attributes are defined" do
89
+ it "returns an empty Hash" do
90
+ expect(::NoAttributes.new.attributes).to eq({})
91
+ end
92
+ end
93
+
94
+ context "when an attribute is defined" do
95
+ it "returns the key value pairs" do
96
+ subject.name = "test"
97
+ expect(subject.attributes).to include("name" => "test")
98
+ end
99
+
100
+ it "returns a new Hash " do
101
+ subject.attributes.merge!("foobar" => "foobar")
102
+ expect(subject.attributes).to_not include("foobar" => "foobar")
103
+ end
104
+
105
+ it "returns all attributes" do
106
+ expect(subject.attributes.keys).to eq(["guid", "name", "user_guid", "chief_editor_guid", "editor_guid", "category_guid"])
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "#inspect" do
112
+ before { subject.name = "test" }
113
+
114
+ it "includes the class name and all attribute values in alphabetical order by attribute name" do
115
+ expect(subject.inspect).to eq(%{#<Author category_guid: nil, chief_editor_guid: nil, editor_guid: nil, guid: nil, name: "test", user_guid: nil>})
116
+ end
117
+
118
+ it "doesn't format the inspection string for attributes if the model does not have any" do
119
+ expect(::NoAttributes.new.inspect).to eq(%{#<NoAttributes>})
120
+ end
121
+ end
122
+
123
+ [:[], :read_attribute].each do |method|
124
+ describe "##{method}" do
125
+ context "when an attribute is not set" do
126
+ it "returns nil" do
127
+ expect(subject.send(method, :name)).to be_nil
128
+ end
129
+ end
130
+
131
+ context "when an attribute is set" do
132
+ before { subject.write_attribute(:name, "test") }
133
+
134
+ it "returns the attribute using a Symbol" do
135
+ expect(subject.send(method, :name)).to eq("test")
136
+ end
137
+
138
+ it "returns the attribute using a String" do
139
+ expect(subject.send(method, "name")).to eq("test")
140
+ end
141
+ end
142
+
143
+ it "raises when getting an undefined attribute" do
144
+ expect { subject.send(method, :foobar) }.to raise_error(::ActiveRemote::UnknownAttributeError)
145
+ end
146
+ end
147
+ end
148
+
149
+ [:[]=, :write_attribute].each do |method|
150
+ describe "##{method}" do
151
+ it "raises ArgumentError with one argument" do
152
+ expect { subject.send(method, :name) }.to raise_error(::ArgumentError)
153
+ end
154
+
155
+ it "raises ArgumentError with no arguments" do
156
+ expect { subject.send(method) }.to raise_error(::ArgumentError)
157
+ end
158
+
159
+ it "sets an attribute using a Symbol and value" do
160
+ expect { subject.send(method, :name, "test") }.to change { subject.attributes["name"] }.from(nil).to("test")
161
+ end
162
+
163
+ it "sets an attribute using a String and value" do
164
+ expect { subject.send(method, "name", "test") }.to change { subject.attributes["name"] }.from(nil).to("test")
165
+ end
166
+
167
+ it "is able to set an attribute to nil" do
168
+ subject.name = "test"
169
+ expect { subject.send(method, :name, nil) }.to change { subject.attributes["name"] }.from("test").to(nil)
170
+ end
171
+
172
+ it "raises when setting an undefined attribute" do
173
+ expect { subject.send(method, :foobar, "test") }.to raise_error(::ActiveRemote::UnknownAttributeError)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -1,16 +1,16 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ActiveRemote::Persistence do
4
- let(:rpc) { Tag.new }
5
- let(:response_without_errors) { HashWithIndifferentAccess.new(:errors => []) }
3
+ describe ::ActiveRemote::Persistence do
4
+ let(:response_without_errors) { ::HashWithIndifferentAccess.new(:errors => []) }
5
+ let(:rpc) { ::ActiveRemote::RPCAdapters::ProtobufAdapter.new(::Tag.service_class) }
6
6
 
7
- subject { Tag.new }
7
+ subject { ::Tag.new }
8
8
 
9
9
  before {
10
10
  allow(rpc).to receive(:execute).and_return(response_without_errors)
11
11
  allow(Tag).to receive(:rpc).and_return(rpc)
12
12
  }
13
- after { allow(Tag).to receive(:rpc).and_call_original }
13
+ after { allow(::Tag).to receive(:rpc).and_call_original }
14
14
 
15
15
  describe ".create" do
16
16
  it "runs create callbacks" do
@@ -251,9 +251,6 @@ describe ActiveRemote::Persistence do
251
251
  end
252
252
 
253
253
  describe "#save!" do
254
- before { allow(subject).to receive(:execute) }
255
- after { allow(subject).to receive(:execute).and_call_original }
256
-
257
254
  context "when the record is saved" do
258
255
  it "returns true" do
259
256
  allow(subject).to receive(:save).and_return(true)
@@ -286,16 +283,13 @@ describe ActiveRemote::Persistence do
286
283
  describe "#update_attribute" do
287
284
  let(:tag) { Tag.allocate.instantiate({:guid => "123"}) }
288
285
 
289
- before { allow(Tag.rpc).to receive(:execute).and_return(HashWithIndifferentAccess.new) }
290
- after { allow(Tag.rpc).to receive(:execute).and_call_original }
291
-
292
286
  it "runs update callbacks" do
293
287
  expect(tag).to receive(:after_update_callback)
294
288
  tag.update_attribute(:name, "foo")
295
289
  end
296
290
 
297
291
  it "updates a remote record" do
298
- expect(Tag.rpc).to receive(:execute).with(:update, {"name" => "foo", "guid" => "123"})
292
+ expect(rpc).to receive(:execute).with(:update, {"name" => "foo", "guid" => "123"})
299
293
  tag.update_attribute(:name, "foo")
300
294
  end
301
295
 
@@ -317,16 +311,13 @@ describe ActiveRemote::Persistence do
317
311
  let(:attributes) { HashWithIndifferentAccess.new(:name => 'bar') }
318
312
  let(:tag) { Tag.allocate.instantiate({:guid => "123"}) }
319
313
 
320
- before { allow(Tag.rpc).to receive(:execute).and_return(HashWithIndifferentAccess.new) }
321
- after { allow(Tag.rpc).to receive(:execute).and_call_original }
322
-
323
314
  it "runs update callbacks" do
324
315
  expect(tag).to receive(:after_update_callback)
325
316
  tag.update_attributes({})
326
317
  end
327
318
 
328
319
  it "updates a remote record" do
329
- expect(Tag.rpc).to receive(:execute).with(:update, tag.scope_key_hash)
320
+ expect(rpc).to receive(:execute).with(:update, tag.scope_key_hash)
330
321
  tag.update_attributes({})
331
322
  end
332
323