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,143 @@
|
|
1
|
+
require 'active_remote/persistence'
|
2
|
+
|
3
|
+
module ActiveRemote
|
4
|
+
module Bulk
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
extend ActiveRemote::Bulk::ClassMethods
|
8
|
+
include ActiveRemote::Persistence
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Class methods
|
14
|
+
#
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Create multiple records at the same time. Returns a collection of active
|
18
|
+
# remote objects from the passed records. Records that were not created
|
19
|
+
# are returned with error messages indicating what went wrong.
|
20
|
+
#
|
21
|
+
# ====Examples
|
22
|
+
#
|
23
|
+
# # A single hash
|
24
|
+
# Tag.create_all({ :name => 'foo' })
|
25
|
+
#
|
26
|
+
# # Hashes
|
27
|
+
# Tag.create_all({ :name => 'foo' }, { :name => 'bar' })
|
28
|
+
#
|
29
|
+
# # Active remote objects
|
30
|
+
# Tag.create_all(Tag.new(:name => 'foo'), Tag.new(:name => 'bar'))
|
31
|
+
#
|
32
|
+
# # Protobuf objects
|
33
|
+
# Tag.create_all(Generic::Remote::Tag.new(:name => 'foo'), Generic::Remote::Tag.new(:name => 'bar'))
|
34
|
+
#
|
35
|
+
# # Bulk protobuf object
|
36
|
+
# Tag.create_all(Generic::Remote::Tags.new(:records => [ Generic::Remote::Tag.new(:name => 'foo') ])
|
37
|
+
#
|
38
|
+
def create_all(*records)
|
39
|
+
remote = self.new
|
40
|
+
remote.execute(:create_all, parse_records(records))
|
41
|
+
remote.serialize_records
|
42
|
+
end
|
43
|
+
|
44
|
+
# Delete multiple records at the same time. Returns a collection of active
|
45
|
+
# remote objects from the passed records. Records that were not deleted
|
46
|
+
# are returned with error messages indicating what went wrong.
|
47
|
+
#
|
48
|
+
# ====Examples
|
49
|
+
#
|
50
|
+
# # A single hash
|
51
|
+
# Tag.delete_all({ :guid => 'foo' })
|
52
|
+
#
|
53
|
+
# # Hashes
|
54
|
+
# Tag.delete_all({ :guid => 'foo' }, { :guid => 'bar' })
|
55
|
+
#
|
56
|
+
# # Active remote objects
|
57
|
+
# Tag.delete_all(Tag.new(:guid => 'foo'), Tag.new(:guid => 'bar'))
|
58
|
+
#
|
59
|
+
# # Protobuf objects
|
60
|
+
# Tag.delete_all(Generic::Remote::Tag.new(:guid => 'foo'), Generic::Remote::Tag.new(:guid => 'bar'))
|
61
|
+
#
|
62
|
+
# # Bulk protobuf object
|
63
|
+
# Tag.delete_all(Generic::Remote::Tags.new(:records => [ Generic::Remote::Tag.new(:guid => 'foo') ])
|
64
|
+
#
|
65
|
+
def delete_all(*records)
|
66
|
+
remote = self.new
|
67
|
+
remote.execute(:delete_all, parse_records(records))
|
68
|
+
remote.serialize_records
|
69
|
+
end
|
70
|
+
|
71
|
+
# Destroy multiple records at the same time. Returns a collection of active
|
72
|
+
# remote objects from the passed records. Records that were not destroyed
|
73
|
+
# are returned with error messages indicating what went wrong.
|
74
|
+
#
|
75
|
+
# ====Examples
|
76
|
+
#
|
77
|
+
# # A single hash
|
78
|
+
# Tag.destroy_all({ :guid => 'foo' })
|
79
|
+
#
|
80
|
+
# # Hashes
|
81
|
+
# Tag.destroy_all({ :guid => 'foo' }, { :guid => 'bar' })
|
82
|
+
#
|
83
|
+
# # Active remote objects
|
84
|
+
# Tag.destroy_all(Tag.new(:guid => 'foo'), Tag.new(:guid => 'bar'))
|
85
|
+
#
|
86
|
+
# # Protobuf objects
|
87
|
+
# Tag.destroy_all(Generic::Remote::Tag.new(:guid => 'foo'), Generic::Remote::Tag.new(:guid => 'bar'))
|
88
|
+
#
|
89
|
+
# # Bulk protobuf object
|
90
|
+
# Tag.destroy_all(Generic::Remote::Tags.new(:records => [ Generic::Remote::Tag.new(:guid => 'foo') ])
|
91
|
+
#
|
92
|
+
def destroy_all(*records)
|
93
|
+
remote = self.new
|
94
|
+
remote.execute(:destroy_all, parse_records(records))
|
95
|
+
remote.serialize_records
|
96
|
+
end
|
97
|
+
|
98
|
+
# Parse given records to get them ready to be built into a request.
|
99
|
+
#
|
100
|
+
# It handles any object that responds to +to_hash+, so protobuf messages
|
101
|
+
# and active remote objects will work just like hashes.
|
102
|
+
#
|
103
|
+
# Returns +{ :records => records }+.
|
104
|
+
#
|
105
|
+
def parse_records(*records)
|
106
|
+
records.flatten!
|
107
|
+
records.collect!(&:to_hash)
|
108
|
+
|
109
|
+
return records.first if records.first.has_key?(:records)
|
110
|
+
|
111
|
+
# If we made it this far, build a bulk-formatted hash.
|
112
|
+
return { :records => records }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Update multiple records at the same time. Returns a collection of active
|
116
|
+
# remote objects from the passed records. Records that were not updated
|
117
|
+
# are returned with error messages indicating what went wrong.
|
118
|
+
#
|
119
|
+
# ====Examples
|
120
|
+
#
|
121
|
+
# # A single hash
|
122
|
+
# Tag.update_all({ :guid => 'foo', :name => 'baz' })
|
123
|
+
#
|
124
|
+
# # Hashes
|
125
|
+
# Tag.update_all({ :guid => 'foo', :name => 'baz' }, { :guid => 'bar', :name => 'qux' })
|
126
|
+
#
|
127
|
+
# # Active remote objects
|
128
|
+
# Tag.update_all(Tag.new(:guid => 'foo', :name => 'baz'), Tag.new(:guid => 'bar', :name => 'qux'))
|
129
|
+
#
|
130
|
+
# # Protobuf objects
|
131
|
+
# Tag.update_all(Generic::Remote::Tag.new(:guid => 'foo', :name => 'baz'), Generic::Remote::Tag.new(:guid => 'bar', :name => 'qux'))
|
132
|
+
#
|
133
|
+
# # Bulk protobuf object
|
134
|
+
# Tag.update_all(Generic::Remote::Tags.new(:records => [ Generic::Remote::Tag.new(:guid => 'foo', :name => 'baz') ])
|
135
|
+
#
|
136
|
+
def update_all(*records)
|
137
|
+
remote = self.new
|
138
|
+
remote.execute(:update_all, parse_records(records))
|
139
|
+
remote.serialize_records
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'active_model/dirty'
|
2
|
+
|
3
|
+
# Overrides persistence methods, providing support for dirty tracking.
|
4
|
+
#
|
5
|
+
module ActiveRemote
|
6
|
+
module Dirty
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
include ActiveModel::Dirty
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Override #reload to provide dirty tracking.
|
14
|
+
#
|
15
|
+
def reload(*)
|
16
|
+
super.tap do
|
17
|
+
@previously_changed.try(:clear)
|
18
|
+
@changed_attributes.clear
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override #save to store changes as previous changes then clear them.
|
23
|
+
#
|
24
|
+
def save(*)
|
25
|
+
if status = super
|
26
|
+
@previously_changed = changes
|
27
|
+
@changed_attributes.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
status
|
31
|
+
end
|
32
|
+
|
33
|
+
# Override #save to store changes as previous changes then clear them.
|
34
|
+
#
|
35
|
+
def save!(*)
|
36
|
+
super.tap do
|
37
|
+
@previously_changed = changes
|
38
|
+
@changed_attributes.clear
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Override #serialize_records so that we can clear changes after
|
43
|
+
# initializing records returned from a search.
|
44
|
+
#
|
45
|
+
def serialize_records(*)
|
46
|
+
if serialized_records = super
|
47
|
+
serialized_records.each do |record|
|
48
|
+
record.previous_changes.try(:clear)
|
49
|
+
record.changed_attributes.try(:clear)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Override ActiveAttr's attribute= method so we can provide support for
|
57
|
+
# ActiveModel::Dirty.
|
58
|
+
#
|
59
|
+
def attribute=(name, value)
|
60
|
+
__send__("#{name}_will_change!") unless value == self[name]
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
# Override #update to only send changed attributes.
|
65
|
+
#
|
66
|
+
def update(*)
|
67
|
+
super(changed)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module ActiveRemote
|
4
|
+
module DSL
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
extend ActiveRemote::DSL::ClassMethods
|
8
|
+
include ActiveRemote::DSL::InstanceMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
# Whitelist enable attributes for serialization purposes.
|
15
|
+
#
|
16
|
+
# ====Examples
|
17
|
+
#
|
18
|
+
# # To only publish the :guid and :status attributes:
|
19
|
+
# class User < ActiveRemote::Base
|
20
|
+
# attr_publishable :guid, :status
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
def attr_publishable(*attributes)
|
24
|
+
@publishable_attributes ||= []
|
25
|
+
@publishable_attributes += attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set the number of records per page when auto paging.
|
29
|
+
#
|
30
|
+
# ====Examples
|
31
|
+
#
|
32
|
+
# class User < ActiveRemote::Base
|
33
|
+
# auto_paging_size 100
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
def auto_paging_size(size=nil)
|
37
|
+
@auto_paging_size = size unless size.nil?
|
38
|
+
@auto_paging_size ||= 1000
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the namespace for the underlying RPC service class. If no namespace
|
42
|
+
# is given, then none will be used.
|
43
|
+
#
|
44
|
+
# ====Examples
|
45
|
+
#
|
46
|
+
# # If the user's service class is namespaced (e.g. Acme::UserService):
|
47
|
+
# class User < ActiveRemote::Base
|
48
|
+
# namespace :acme
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
def namespace(name = false)
|
52
|
+
@namespace = name unless name == false
|
53
|
+
@namespace
|
54
|
+
end
|
55
|
+
|
56
|
+
# Retrieve the attributes that have been whitelisted for serialization.
|
57
|
+
#
|
58
|
+
def publishable_attributes
|
59
|
+
@publishable_attributes
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set the RPC service class directly. By default, ActiveRemove determines
|
63
|
+
# the RPC service by constantizing the namespace and service name.
|
64
|
+
#
|
65
|
+
# ====Examples
|
66
|
+
#
|
67
|
+
# class User < ActiveRemote::Base
|
68
|
+
# service_class Acme::UserService
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# # ...is the same as:
|
72
|
+
#
|
73
|
+
# class User < ActiveRemote::Base
|
74
|
+
# namespace :acme
|
75
|
+
# service_name :user_service
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# # ...is the same as:
|
79
|
+
#
|
80
|
+
# class User < ActiveRemote::Base
|
81
|
+
# namespace :acme
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
def service_class(klass = false)
|
85
|
+
@service_class = klass unless klass == false
|
86
|
+
@service_class ||= _determine_service_class
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set the name of the underlying RPC service class. By default, Active
|
90
|
+
# Remote assumes that a User model will have a UserService (making the
|
91
|
+
# service name :user_service).
|
92
|
+
#
|
93
|
+
# ====Examples
|
94
|
+
#
|
95
|
+
# class User < ActiveRemote::Base
|
96
|
+
# service_name :jangly_users
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
def service_name(name = false)
|
100
|
+
@service_name = name unless name == false
|
101
|
+
@service_name ||= _determine_service_name
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Combine the namespace and service values, constantize them and return
|
107
|
+
# the class constant.
|
108
|
+
#
|
109
|
+
def _determine_service_class
|
110
|
+
class_name = [ namespace, service_name ].join("/")
|
111
|
+
const_name = class_name.camelize
|
112
|
+
|
113
|
+
return const_name.present? ? const_name.constantize : const_name
|
114
|
+
end
|
115
|
+
|
116
|
+
def _determine_service_name
|
117
|
+
underscored_name = self.name.underscore
|
118
|
+
"#{underscored_name}_service".to_sym
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Convenience methods for accessing DSL methods in instances.
|
123
|
+
#
|
124
|
+
module InstanceMethods
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def _publishable_attributes
|
129
|
+
self.class.publishable_attributes
|
130
|
+
end
|
131
|
+
|
132
|
+
def _service_name
|
133
|
+
self.class.service_name
|
134
|
+
end
|
135
|
+
|
136
|
+
def _service_class
|
137
|
+
self.class.service_class
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# TODO: Create more specific errors
|
2
|
+
#
|
3
|
+
module ActiveRemote
|
4
|
+
|
5
|
+
# = Active Remote Errors
|
6
|
+
#
|
7
|
+
# Generic Active Remote exception class.
|
8
|
+
class ActiveRemoteError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised by ActiveRemove::Base.save when the remote record is readonly.
|
12
|
+
class ReadOnlyRemoteRecord < ActiveRemoteError
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raised by ActiveRemove::Base.find when remote record is not found when
|
16
|
+
# searching with the given arguments.
|
17
|
+
class RemoteRecordNotFound < ActiveRemoteError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Raised by ActiveRemove::Base.save! and ActiveRemote::Base.create! methods
|
21
|
+
# when remote record cannot be saved because it is invalid.
|
22
|
+
class RemoteRecordNotSaved < ActiveRemoteError
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'active_remote/rpc'
|
2
|
+
|
3
|
+
module ActiveRemote
|
4
|
+
module Persistence
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
extend ActiveRemote::Persistence::ClassMethods
|
8
|
+
include ActiveRemote::Persistence::InstanceMethods
|
9
|
+
include ActiveRemote::RPC
|
10
|
+
|
11
|
+
define_model_callbacks :save
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Class methods
|
17
|
+
#
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# Creates a remote record through the service.
|
21
|
+
#
|
22
|
+
# The service will run any validations and if any of them fail, will return
|
23
|
+
# the record error messages indicating what went wrong.
|
24
|
+
#
|
25
|
+
# The newly created record is returned if it was successfully saved or not.
|
26
|
+
#
|
27
|
+
def create(attributes)
|
28
|
+
remote = self.new(attributes)
|
29
|
+
remote.save
|
30
|
+
remote
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a remote record through the service and if successful, returns
|
34
|
+
# the newly created record.
|
35
|
+
#
|
36
|
+
# The service will run any validations and if any of them fail, will raise
|
37
|
+
# an ActiveRemote::RemoteRecordNotSaved exception.
|
38
|
+
#
|
39
|
+
def create!(attributes)
|
40
|
+
remote = self.new(attributes)
|
41
|
+
remote.save!
|
42
|
+
remote
|
43
|
+
end
|
44
|
+
|
45
|
+
# Mark the class as readonly. Overrides instance-level readonly, making
|
46
|
+
# any instance of this class readonly.
|
47
|
+
def readonly!
|
48
|
+
@readonly = true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true if the class is marked as readonly; otherwise, returns false.
|
52
|
+
def readonly?
|
53
|
+
@readonly
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Instance methods
|
59
|
+
#
|
60
|
+
module InstanceMethods
|
61
|
+
# Deletes the record from the service (the service determines if the
|
62
|
+
# record is hard or soft deleted) and freezes this instance to indicate
|
63
|
+
# that no changes should be made (since they can't be persisted). If the
|
64
|
+
# record was not deleted, it will have error messages indicating what went
|
65
|
+
# wrong. Returns the frozen instance.
|
66
|
+
#
|
67
|
+
def delete
|
68
|
+
raise ReadOnlyRemoteRecord if readonly?
|
69
|
+
execute(:delete, attributes.slice("guid"))
|
70
|
+
freeze if success?
|
71
|
+
end
|
72
|
+
|
73
|
+
# Deletes the record from the service (the service determines if the
|
74
|
+
# record is hard or soft deleted) and freezes this instance to indicate
|
75
|
+
# that no changes should be made (since they can't be persisted). If the
|
76
|
+
# record was not deleted, an exception will be raised. Returns the frozen
|
77
|
+
# instance.
|
78
|
+
#
|
79
|
+
def delete!
|
80
|
+
delete
|
81
|
+
raise ActiveRemoteError.new(errors.to_s) if has_errors?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Destroys (hard deletes) the record from the service and freezes this
|
85
|
+
# instance to indicate that no changes should be made (since they can't
|
86
|
+
# be persisted). If the record was not deleted, it will have error
|
87
|
+
# messages indicating what went wrong. Returns the frozen instance.
|
88
|
+
#
|
89
|
+
def destroy
|
90
|
+
raise ReadOnlyRemoteRecord if readonly?
|
91
|
+
execute(:destroy, attributes.slice("guid"))
|
92
|
+
freeze if success?
|
93
|
+
end
|
94
|
+
|
95
|
+
# Destroys (hard deletes) the record from the service and freezes this
|
96
|
+
# instance to indicate that no changes should be made (since they can't
|
97
|
+
# be persisted). If the record was not deleted, an exception will be
|
98
|
+
# raised. Returns the frozen instance.
|
99
|
+
#
|
100
|
+
def destroy!
|
101
|
+
destroy
|
102
|
+
raise ActiveRemoteError.new(errors.to_s) if has_errors?
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns true if the record has errors; otherwise, returns false.
|
106
|
+
#
|
107
|
+
def has_errors?
|
108
|
+
return respond_to?(:errors) && errors.present?
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true if the remote record hasn't been saved yet; otherwise,
|
112
|
+
# returns false.
|
113
|
+
#
|
114
|
+
def new_record?
|
115
|
+
return self[:guid].nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns true if the remote record has been saved; otherwise, returns false.
|
119
|
+
#
|
120
|
+
def persisted?
|
121
|
+
return ! new_record?
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns true if the remote class or remote record is readonly; otherwise, returns false.
|
125
|
+
def readonly?
|
126
|
+
self.class.readonly? || @readonly
|
127
|
+
end
|
128
|
+
|
129
|
+
# Saves the remote record.
|
130
|
+
#
|
131
|
+
# If it is a new record, it will be created through the service, otherwise
|
132
|
+
# the existing record gets updated.
|
133
|
+
#
|
134
|
+
# The service will run any validations and if any of them fail, will return
|
135
|
+
# the record with error messages indicating what went wrong.
|
136
|
+
#
|
137
|
+
# Also runs any before/after save callbacks that are defined.
|
138
|
+
#
|
139
|
+
def save
|
140
|
+
run_callbacks :save do
|
141
|
+
create_or_update
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Saves the remote record.
|
146
|
+
#
|
147
|
+
# If it is a new record, it will be created through the service, otherwise
|
148
|
+
# the existing record gets updated.
|
149
|
+
#
|
150
|
+
# The service will run any validations. If any of them fail (e.g. error
|
151
|
+
# messages are returned), an ActiveRemote::RemoteRecordNotSaved is raised.
|
152
|
+
#
|
153
|
+
# Also runs any before/after save callbacks that are defined.
|
154
|
+
#
|
155
|
+
def save!
|
156
|
+
save || raise(RemoteRecordNotSaved)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns true if the record doesn't have errors; otherwise, returns false.
|
160
|
+
#
|
161
|
+
def success?
|
162
|
+
return ! has_errors?
|
163
|
+
end
|
164
|
+
|
165
|
+
# Updates the attributes of the remote record from the passed-in hash and
|
166
|
+
# saves the remote record. If the object is invalid, it will have error
|
167
|
+
# messages and false will be returned.
|
168
|
+
#
|
169
|
+
def update_attributes(attributes)
|
170
|
+
assign_attributes(attributes)
|
171
|
+
save
|
172
|
+
end
|
173
|
+
|
174
|
+
# Updates the attributes of the remote record from the passed-in hash and
|
175
|
+
# saves the remote record. If the object is invalid, an
|
176
|
+
# ActiveRemote::RemoteRecordNotSaved is raised.
|
177
|
+
#
|
178
|
+
def update_attributes!(attributes)
|
179
|
+
assign_attributes(attributes)
|
180
|
+
save!
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
# Handles creating a remote object and serializing it's attributes and
|
186
|
+
# errors from the response.
|
187
|
+
#
|
188
|
+
def create
|
189
|
+
new_attributes = attributes.deep_dup
|
190
|
+
new_attributes.delete("guid")
|
191
|
+
|
192
|
+
execute(:create, new_attributes)
|
193
|
+
|
194
|
+
assign_attributes(last_response.to_hash)
|
195
|
+
add_errors_from_response
|
196
|
+
|
197
|
+
success?
|
198
|
+
end
|
199
|
+
|
200
|
+
# Deterines whether the record should be created or updated. New records
|
201
|
+
# are created, existing records are updated. If the record is marked as
|
202
|
+
# readonly, an ActiveRemote::ReadOnlyRemoteRecord is raised.
|
203
|
+
#
|
204
|
+
def create_or_update
|
205
|
+
raise ReadOnlyRemoteRecord if readonly?
|
206
|
+
new_record? ? create : update
|
207
|
+
end
|
208
|
+
|
209
|
+
# Handles updating a remote object and serializing it's attributes and
|
210
|
+
# errors from the response. Only attributes with the given attribute names
|
211
|
+
# (plus :guid) will be updated. Defaults to all attributes.
|
212
|
+
#
|
213
|
+
def update(attribute_names = @attributes.keys)
|
214
|
+
updated_attributes = attributes.slice(*attribute_names)
|
215
|
+
updated_attributes.merge!("guid" => self[:guid])
|
216
|
+
|
217
|
+
execute(:update, updated_attributes)
|
218
|
+
|
219
|
+
assign_attributes(last_response.to_hash)
|
220
|
+
add_errors_from_response
|
221
|
+
|
222
|
+
success?
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|