active_remote 2.4.0 → 3.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -0
- data/CHANGES.md +22 -0
- data/README.md +2 -0
- data/active_remote.gemspec +2 -2
- data/bin/console +10 -0
- data/lib/active_remote/association.rb +4 -0
- data/lib/active_remote/attribute_assignment.rb +53 -0
- data/lib/active_remote/attribute_definition.rb +106 -0
- data/lib/active_remote/attributes.rb +165 -8
- data/lib/active_remote/base.rb +41 -36
- data/lib/active_remote/dirty.rb +0 -9
- data/lib/active_remote/errors.rb +6 -0
- data/lib/active_remote/persistence.rb +10 -19
- data/lib/active_remote/query_attributes.rb +45 -0
- data/lib/active_remote/rpc.rb +17 -67
- data/lib/active_remote/search.rb +0 -20
- data/lib/active_remote/serialization.rb +1 -32
- data/lib/active_remote/typecasting.rb +3 -12
- data/lib/active_remote/version.rb +1 -1
- data/lib/active_remote.rb +0 -2
- data/spec/lib/active_remote/association_spec.rb +11 -2
- data/spec/lib/active_remote/attributes_spec.rb +177 -0
- data/spec/lib/active_remote/persistence_spec.rb +7 -16
- data/spec/lib/active_remote/query_attribute_spec.rb +171 -0
- data/spec/lib/active_remote/rpc_spec.rb +33 -75
- data/spec/lib/active_remote/search_spec.rb +0 -21
- data/spec/lib/active_remote/serialization_spec.rb +0 -23
- data/spec/support/models/no_attributes.rb +2 -0
- data/spec/support/models.rb +1 -0
- metadata +21 -66
- data/lib/active_remote/attribute_defaults.rb +0 -100
- data/lib/active_remote/bulk.rb +0 -168
- data/lib/active_remote/core_ext/date.rb +0 -7
- data/lib/active_remote/core_ext/date_time.rb +0 -7
- data/lib/active_remote/core_ext/integer.rb +0 -19
- data/lib/active_remote/core_ext.rb +0 -3
- data/lib/active_remote/publication.rb +0 -54
- data/lib/active_remote/serializers/json.rb +0 -16
- data/spec/core_ext/date_time_spec.rb +0 -9
- data/spec/lib/active_remote/attribute_defaults_spec.rb +0 -26
- data/spec/lib/active_remote/bulk_spec.rb +0 -83
- data/spec/lib/active_remote/publication_spec.rb +0 -18
- data/spec/lib/active_remote/serializers/json_spec.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6501c9a34d84239729afd215a01cea574af7a5c
|
4
|
+
data.tar.gz: 55a6b558a7fa1fba40a415266743f7e01fc31fc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57f951cd6dc0f19d37942880c2da4066d9cd7773c1ee16def3c0068cfb337d68aa4f596b80fe679ef1e8f83bf5685a5f5396b8a9204293ca273f0db24428ef78
|
7
|
+
data.tar.gz: f861f24788ce54b8dfaa0693ee236cc5aad887945bba35670687b9a8fb57f56f4eb06ec9e090ce052eef1c8f4242d6c026910d92f607dd79c5f1b07d700e11dc
|
data/CHANGES.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# ActiveRemote Changes
|
2
|
+
|
3
|
+
3.0.0
|
4
|
+
----------
|
5
|
+
|
6
|
+
- Remove dependency on ActiveAttr [#48]
|
7
|
+
- Remove core exts [#49]
|
8
|
+
- Remove deprecated rpc methods `.request`, `.request_type`, #execute`, `#remote_call`
|
9
|
+
These methods are handled by the rpc adater now. [#49]
|
10
|
+
- Remove deprecated method `._active_remote_search_args` [#49]
|
11
|
+
- Remove deprecated `.parse_records` method [#49]
|
12
|
+
- Remove publication, `#publishable_hash` method [#49]
|
13
|
+
- Drop support for Rails 3 mass assignment protection. Add support for strong param
|
14
|
+
enforcement for Rails 4+. [#50]
|
15
|
+
- Improve performance of many methods including `respond_to?` and `new`. [#50]
|
16
|
+
- Refactor of attribute storage internals [#50]
|
17
|
+
- Remove a method was was doign dirty tracking twice [#52]
|
18
|
+
- Extracted bulk methods to active_remote-bulk [#54]
|
19
|
+
- Removed search callbacks [#55]
|
20
|
+
- Refactor of instantiate from rpc codepath [#56]
|
21
|
+
- Change to internals of typecasting. Declaring `attribute :name, :type => Integer`
|
22
|
+
will no longer affect performance negatively. [#56]
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/liveh2o/active_remote.svg?branch=master)](https://travis-ci.org/liveh2o/active_remote)
|
2
|
+
|
1
3
|
# Active Remote
|
2
4
|
|
3
5
|
Active Remote provides [Active Record](https://github.com/rails/rails/tree/master/activerecord)-like object-relational mapping over RPC. Think of it as Active Record for your platform: within a service, use Active Record to persist objects and between services, use Active Remote.
|
data/active_remote.gemspec
CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
##
|
20
20
|
# Dependencies
|
21
21
|
#
|
22
|
-
s.add_dependency "
|
23
|
-
s.add_dependency "activesupport", ">=
|
22
|
+
s.add_dependency "activemodel", ">= 4.0"
|
23
|
+
s.add_dependency "activesupport", ">= 4.0"
|
24
24
|
s.add_dependency "protobuf", ">= 3.0"
|
25
25
|
|
26
26
|
##
|
data/bin/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "active_remote"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
require "pry"
|
10
|
+
Pry.start
|
@@ -83,6 +83,9 @@ module ActiveRemote
|
|
83
83
|
|
84
84
|
search_hash.values.any?(&:nil?) ? [] : klass.search(search_hash)
|
85
85
|
end
|
86
|
+
|
87
|
+
options.merge!(:has_many => true)
|
88
|
+
create_setter_method(has_many_class, options)
|
86
89
|
end
|
87
90
|
|
88
91
|
# Create a `has_one` association for a given remote resource.
|
@@ -135,6 +138,7 @@ module ActiveRemote
|
|
135
138
|
def create_setter_method(associated_klass, options={})
|
136
139
|
writer_method = "#{associated_klass}=".to_sym
|
137
140
|
define_method(writer_method) do |new_value|
|
141
|
+
raise "New value must be an array" if options[:has_many] == true && new_value.class != Array
|
138
142
|
klass_name = options.fetch(:class_name){ associated_klass }
|
139
143
|
klass = klass_name.to_s.classify.constantize
|
140
144
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
2
|
+
|
3
|
+
# Added ActiveModel AttributeAssignment to support Rails 4.
|
4
|
+
# In Rails 5 this is built in to ActiveModel, but our behavior
|
5
|
+
# in ActiveRemote is different.
|
6
|
+
module ActiveRemote
|
7
|
+
module AttributeAssignment
|
8
|
+
include ::ActiveModel::ForbiddenAttributesProtection
|
9
|
+
|
10
|
+
# Allows you to set all the attributes by passing in a hash of attributes with
|
11
|
+
# keys matching the attribute names.
|
12
|
+
#
|
13
|
+
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
14
|
+
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
15
|
+
# exception is raised.
|
16
|
+
#
|
17
|
+
# class Cat
|
18
|
+
# include ActiveModel::AttributeAssignment
|
19
|
+
# attr_accessor :name, :status
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# cat = Cat.new
|
23
|
+
# cat.assign_attributes(name: "Gorby", status: "yawning")
|
24
|
+
# cat.name # => 'Gorby'
|
25
|
+
# cat.status => 'yawning'
|
26
|
+
# cat.assign_attributes(status: "sleeping")
|
27
|
+
# cat.name # => 'Gorby'
|
28
|
+
# cat.status => 'sleeping'
|
29
|
+
def assign_attributes(new_attributes)
|
30
|
+
if !new_attributes.respond_to?(:stringify_keys)
|
31
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
32
|
+
end
|
33
|
+
return if new_attributes.nil? || new_attributes.empty?
|
34
|
+
|
35
|
+
attributes = new_attributes.stringify_keys
|
36
|
+
_assign_attributes(sanitize_for_mass_assignment(attributes))
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def _assign_attributes(attributes)
|
42
|
+
attributes.each do |name, value|
|
43
|
+
_assign_attribute(name, value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# ActiveRemote silently ignores unknown attributes, unlike ActiveModel
|
48
|
+
# Consider changing ActiveRemote to behave the same
|
49
|
+
def _assign_attribute(name, value)
|
50
|
+
public_send("#{name}=", value) if respond_to?("#{name}=")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module ActiveRemote
|
2
|
+
# Represents an attribute for reflection
|
3
|
+
#
|
4
|
+
# @example Usage
|
5
|
+
# AttributeDefinition.new(:amount)
|
6
|
+
#
|
7
|
+
# @since 0.2.0
|
8
|
+
class AttributeDefinition
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
# The attribute name
|
12
|
+
# @since 0.2.0
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Compare attribute definitions
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# attribute_definition <=> other
|
19
|
+
#
|
20
|
+
# @param [ActiveAttr::AttributeDefinition, Object] other The other
|
21
|
+
# attribute definition to compare with.
|
22
|
+
#
|
23
|
+
# @return [-1, 0, 1, nil]
|
24
|
+
#
|
25
|
+
# @since 0.2.1
|
26
|
+
def <=>(other)
|
27
|
+
return nil unless other.instance_of? self.class
|
28
|
+
return nil if name == other.name && options != other.options
|
29
|
+
self.name.to_s <=> other.name.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
# Read an attribute option
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# attribute_definition[:type]
|
36
|
+
#
|
37
|
+
# @param [Symbol] key The option key
|
38
|
+
#
|
39
|
+
# @since 0.5.0
|
40
|
+
def [](key)
|
41
|
+
@options[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a new AttributeDefinition
|
45
|
+
#
|
46
|
+
# @example Create an attribute defintion
|
47
|
+
# AttributeDefinition.new(:amount)
|
48
|
+
#
|
49
|
+
# @param [Symbol, String, #to_sym] name attribute name
|
50
|
+
# @param [Hash{Symbol => Object}] options attribute options
|
51
|
+
#
|
52
|
+
# @return [ActiveAttr::AttributeDefinition]
|
53
|
+
#
|
54
|
+
# @since 0.2.0
|
55
|
+
def initialize(name, options={})
|
56
|
+
raise TypeError, "can't convert #{name.class} into Symbol" unless name.respond_to? :to_sym
|
57
|
+
@name = name.to_sym
|
58
|
+
@options = options
|
59
|
+
|
60
|
+
if @options[:type]
|
61
|
+
typecaster = ::ActiveRemote::Typecasting::TYPECASTER_MAP[@options[:type]]
|
62
|
+
fail ::ActiveRemote::UnknownType unless typecaster
|
63
|
+
@options[:typecaster] = typecaster
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the code that would generate the attribute definition
|
68
|
+
#
|
69
|
+
# @example Inspect the attribute definition
|
70
|
+
# attribute.inspect
|
71
|
+
#
|
72
|
+
# @return [String] Human-readable presentation of the attribute
|
73
|
+
# definition
|
74
|
+
#
|
75
|
+
# @since 0.6.0
|
76
|
+
def inspect
|
77
|
+
options_description = options.map { |key, value| "#{key.inspect} => #{value.inspect}" }.sort.join(", ")
|
78
|
+
inspected_options = ", #{options_description}" unless options_description.empty?
|
79
|
+
"attribute :#{name}#{inspected_options}"
|
80
|
+
end
|
81
|
+
|
82
|
+
# The attribute name
|
83
|
+
#
|
84
|
+
# @return [String] the attribute name
|
85
|
+
#
|
86
|
+
# @since 0.2.0
|
87
|
+
def to_s
|
88
|
+
name.to_s
|
89
|
+
end
|
90
|
+
|
91
|
+
# The attribute name
|
92
|
+
#
|
93
|
+
# @return [Symbol] the attribute name
|
94
|
+
#
|
95
|
+
# @since 0.2.1
|
96
|
+
def to_sym
|
97
|
+
name
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
# The attribute options
|
103
|
+
# @since 0.5.0
|
104
|
+
attr_reader :options
|
105
|
+
end
|
106
|
+
end
|
@@ -1,22 +1,47 @@
|
|
1
1
|
module ActiveRemote
|
2
2
|
module Attributes
|
3
|
+
extend ::ActiveSupport::Concern
|
4
|
+
include ::ActiveModel::AttributeMethods
|
5
|
+
|
6
|
+
included do
|
7
|
+
attribute_method_suffix "="
|
8
|
+
end
|
9
|
+
|
10
|
+
# Performs equality checking on the result of attributes and its type.
|
11
|
+
#
|
12
|
+
# @example Compare for equality.
|
13
|
+
# model == other
|
14
|
+
#
|
15
|
+
def ==(other)
|
16
|
+
return false unless other.instance_of?(self.class)
|
17
|
+
attributes == other.attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a copy of our attributes hash
|
3
21
|
def attributes
|
4
|
-
@attributes ||= begin
|
5
|
-
attribute_names = self.class.attribute_names
|
6
|
-
Hash[attribute_names.map { |key| [key, send(key)] }]
|
7
|
-
end
|
8
22
|
@attributes.dup
|
9
23
|
end
|
10
24
|
|
25
|
+
# Returns the class name plus its attributes
|
26
|
+
#
|
27
|
+
# @example Inspect the model.
|
28
|
+
# person.inspect
|
29
|
+
#
|
30
|
+
def inspect
|
31
|
+
attribute_descriptions = attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
|
32
|
+
separator = " " unless attribute_descriptions.empty?
|
33
|
+
"#<#{self.class.name}#{separator}#{attribute_descriptions}>"
|
34
|
+
end
|
35
|
+
|
11
36
|
# Read attribute from the attributes hash
|
12
37
|
#
|
13
38
|
def read_attribute(name)
|
14
39
|
name = name.to_s
|
15
40
|
|
16
|
-
if respond_to?
|
41
|
+
if respond_to?(name)
|
17
42
|
attribute(name)
|
18
43
|
else
|
19
|
-
raise
|
44
|
+
raise ::ActiveRemote::UnknownAttributeError, "unknown attribute: #{name}"
|
20
45
|
end
|
21
46
|
end
|
22
47
|
alias_method :[], :read_attribute
|
@@ -26,12 +51,144 @@ module ActiveRemote
|
|
26
51
|
def write_attribute(name, value)
|
27
52
|
name = name.to_s
|
28
53
|
|
29
|
-
if respond_to?
|
54
|
+
if respond_to?("#{name}=")
|
30
55
|
__send__("attribute=", name, value)
|
31
56
|
else
|
32
|
-
raise
|
57
|
+
raise ::ActiveRemote::UnknownAttributeError, "unknown attribute: #{name}"
|
33
58
|
end
|
34
59
|
end
|
35
60
|
alias_method :[]=, :write_attribute
|
61
|
+
|
62
|
+
# Read an attribute from the attributes hash
|
63
|
+
#
|
64
|
+
def attribute(name)
|
65
|
+
@attributes[name]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Write an attribute to the attributes hash
|
69
|
+
#
|
70
|
+
def attribute=(name, value)
|
71
|
+
@attributes[name] = value
|
72
|
+
end
|
73
|
+
|
74
|
+
def attribute_method?(attr_name)
|
75
|
+
# Check if @attributes is defined because dangerous_attribute? method
|
76
|
+
# can check allocate.respond_to? before actaully calling initialize
|
77
|
+
defined?(@attributes) && @attributes.key?(attr_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
# Defines an attribute
|
82
|
+
#
|
83
|
+
# For each attribute that is defined, a getter and setter will be
|
84
|
+
# added as an instance method to the model. An
|
85
|
+
# {AttributeDefinition} instance will be added to result of the
|
86
|
+
# attributes class method.
|
87
|
+
#
|
88
|
+
# @example Define an attribute.
|
89
|
+
# attribute :name
|
90
|
+
#
|
91
|
+
def attribute(name, options={})
|
92
|
+
if dangerous_attribute_method_name = dangerous_attribute?(name)
|
93
|
+
raise ::ActiveRemote::DangerousAttributeError, %{an attribute method named "#{dangerous_attribute_method_name}" would conflict with an existing method}
|
94
|
+
else
|
95
|
+
attribute!(name, options)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Defines an attribute without checking for conflicts
|
100
|
+
#
|
101
|
+
# Allows you to define an attribute whose methods will conflict
|
102
|
+
# with an existing method. For example, Ruby's Timeout library
|
103
|
+
# adds a timeout method to Object. Attempting to define a timeout
|
104
|
+
# attribute using .attribute will raise a
|
105
|
+
# {DangerousAttributeError}, but .attribute! will not.
|
106
|
+
#
|
107
|
+
# @example Define a dangerous attribute.
|
108
|
+
# attribute! :timeout
|
109
|
+
#
|
110
|
+
def attribute!(name, options={})
|
111
|
+
::ActiveRemote::AttributeDefinition.new(name, options).tap do |attribute_definition|
|
112
|
+
attribute_name = attribute_definition.name.to_s
|
113
|
+
# Force active model to generate attribute methods
|
114
|
+
remove_instance_variable("@attribute_methods_generated") if instance_variable_defined?("@attribute_methods_generated")
|
115
|
+
define_attribute_methods([attribute_definition.name]) unless attribute_names.include?(attribute_name)
|
116
|
+
attributes[attribute_name] = attribute_definition
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns an Array of attribute names as Strings
|
121
|
+
#
|
122
|
+
# @example Get attribute names
|
123
|
+
# Person.attribute_names
|
124
|
+
#
|
125
|
+
def attribute_names
|
126
|
+
attributes.keys
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a Hash of AttributeDefinition instances
|
130
|
+
#
|
131
|
+
# @example Get attribute definitions
|
132
|
+
# Person.attributes
|
133
|
+
#
|
134
|
+
def attributes
|
135
|
+
@attributes ||= ::ActiveSupport::HashWithIndifferentAccess.new
|
136
|
+
end
|
137
|
+
|
138
|
+
# Determine if a given attribute name is dangerous
|
139
|
+
#
|
140
|
+
# Some attribute names can cause conflicts with existing methods
|
141
|
+
# on an object. For example, an attribute named "timeout" would
|
142
|
+
# conflict with the timeout method that Ruby's Timeout library
|
143
|
+
# mixes into Object.
|
144
|
+
#
|
145
|
+
# @example Testing a harmless attribute
|
146
|
+
# Person.dangerous_attribute? :name #=> false
|
147
|
+
#
|
148
|
+
# @example Testing a dangerous attribute
|
149
|
+
# Person.dangerous_attribute? :timeout #=> "timeout"
|
150
|
+
#
|
151
|
+
def dangerous_attribute?(name)
|
152
|
+
return false if attribute_names.include?(name.to_s)
|
153
|
+
|
154
|
+
attribute_methods(name).detect do |method_name|
|
155
|
+
allocate.respond_to?(method_name, true)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the class name plus its attribute names
|
160
|
+
#
|
161
|
+
# @example Inspect the model's definition.
|
162
|
+
# Person.inspect
|
163
|
+
#
|
164
|
+
def inspect
|
165
|
+
inspected_attributes = attribute_names.sort
|
166
|
+
attributes_list = "(#{inspected_attributes.join(", ")})" unless inspected_attributes.empty?
|
167
|
+
"#{name}#{attributes_list}"
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
# Assign a set of attribute definitions, used when subclassing models
|
173
|
+
#
|
174
|
+
def attributes=(attributes)
|
175
|
+
@attributes = attributes
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
# Expand an attribute name into its generated methods names
|
181
|
+
#
|
182
|
+
def attribute_methods(name)
|
183
|
+
attribute_method_matchers.map { |matcher| matcher.method_name(name) }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Ruby inherited hook to assign superclass attributes to subclasses
|
187
|
+
#
|
188
|
+
def inherited(subclass)
|
189
|
+
super
|
190
|
+
subclass.attributes = attributes.dup
|
191
|
+
end
|
192
|
+
end
|
36
193
|
end
|
37
194
|
end
|
data/lib/active_remote/base.rb
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
require 'active_model/callbacks'
|
2
|
-
require 'active_attr/model'
|
3
2
|
|
4
3
|
require 'active_remote/association'
|
5
|
-
require 'active_remote/
|
4
|
+
require 'active_remote/attribute_assignment'
|
5
|
+
require 'active_remote/attribute_definition'
|
6
6
|
require 'active_remote/attributes'
|
7
|
-
require 'active_remote/bulk'
|
8
7
|
require 'active_remote/config'
|
9
8
|
require 'active_remote/dirty'
|
10
9
|
require 'active_remote/dsl'
|
11
10
|
require 'active_remote/integration'
|
12
11
|
require 'active_remote/persistence'
|
13
12
|
require 'active_remote/primary_key'
|
14
|
-
require 'active_remote/
|
13
|
+
require 'active_remote/query_attributes'
|
15
14
|
require 'active_remote/rpc'
|
16
15
|
require 'active_remote/scope_keys'
|
17
16
|
require 'active_remote/search'
|
@@ -21,60 +20,66 @@ require 'active_remote/validations'
|
|
21
20
|
|
22
21
|
module ActiveRemote
|
23
22
|
class Base
|
24
|
-
extend ActiveModel::Callbacks
|
25
|
-
|
26
|
-
|
27
|
-
include
|
28
|
-
include
|
29
|
-
|
30
|
-
include
|
31
|
-
include
|
32
|
-
include
|
33
|
-
include
|
34
|
-
include
|
35
|
-
|
36
|
-
include
|
37
|
-
include
|
38
|
-
include
|
39
|
-
include
|
40
|
-
include
|
41
|
-
include
|
42
|
-
include
|
43
|
-
include PrimaryKey
|
44
|
-
include Publication
|
45
|
-
include RPC
|
46
|
-
include ScopeKeys
|
47
|
-
include Search
|
48
|
-
include Serialization
|
49
|
-
include Typecasting
|
23
|
+
extend ::ActiveModel::Callbacks
|
24
|
+
extend ::ActiveModel::Naming
|
25
|
+
|
26
|
+
include ::ActiveModel::Conversion
|
27
|
+
include ::ActiveModel::Validations
|
28
|
+
|
29
|
+
include ::ActiveRemote::Association
|
30
|
+
include ::ActiveRemote::AttributeAssignment
|
31
|
+
include ::ActiveRemote::Attributes
|
32
|
+
include ::ActiveRemote::DSL
|
33
|
+
include ::ActiveRemote::Integration
|
34
|
+
include ::ActiveRemote::Persistence
|
35
|
+
include ::ActiveRemote::PrimaryKey
|
36
|
+
include ::ActiveRemote::QueryAttributes
|
37
|
+
include ::ActiveRemote::RPC
|
38
|
+
include ::ActiveRemote::ScopeKeys
|
39
|
+
include ::ActiveRemote::Search
|
40
|
+
include ::ActiveRemote::Serialization
|
41
|
+
include ::ActiveRemote::Typecasting
|
50
42
|
|
51
43
|
# Overrides some methods, providing support for dirty tracking,
|
52
44
|
# so it needs to be included last.
|
53
|
-
include Dirty
|
45
|
+
include ::ActiveRemote::Dirty
|
54
46
|
|
55
47
|
# Overrides persistence methods, so it must included after
|
56
|
-
include Validations
|
57
|
-
include ActiveModel::Validations::Callbacks
|
48
|
+
include ::ActiveRemote::Validations
|
49
|
+
include ::ActiveModel::Validations::Callbacks
|
58
50
|
|
59
51
|
attr_reader :last_request, :last_response
|
60
52
|
|
61
53
|
define_model_callbacks :initialize, :only => :after
|
62
54
|
|
63
|
-
def initialize(
|
55
|
+
def initialize(attributes = {})
|
64
56
|
@attributes ||= begin
|
65
57
|
attribute_names = self.class.attribute_names
|
66
|
-
Hash[attribute_names.map { |key| [key,
|
58
|
+
Hash[attribute_names.map { |key| [key, nil] }]
|
67
59
|
end
|
68
60
|
|
61
|
+
assign_attributes(attributes) if attributes
|
62
|
+
|
69
63
|
@new_record = true
|
70
64
|
|
71
65
|
skip_dirty_tracking do
|
72
66
|
run_callbacks :initialize do
|
73
|
-
|
67
|
+
yield self if block_given?
|
74
68
|
end
|
75
69
|
end
|
76
70
|
end
|
77
71
|
|
72
|
+
# Initialize an object with the attributes hash directly
|
73
|
+
# When used with allocate, bypasses initialize
|
74
|
+
def init_with(attributes)
|
75
|
+
@attributes = attributes
|
76
|
+
@new_record = false
|
77
|
+
|
78
|
+
run_callbacks :initialize
|
79
|
+
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
78
83
|
def freeze
|
79
84
|
@attributes.freeze; self
|
80
85
|
end
|
data/lib/active_remote/dirty.rb
CHANGED
@@ -57,15 +57,6 @@ module ActiveRemote
|
|
57
57
|
enable_dirty_tracking
|
58
58
|
end
|
59
59
|
|
60
|
-
# Override #write_attribute (along with #[]=) so we can provide support for
|
61
|
-
# ActiveModel::Dirty.
|
62
|
-
#
|
63
|
-
def write_attribute(name, value)
|
64
|
-
__send__("#{name}_will_change!") if _active_remote_track_changes? && value != self[name]
|
65
|
-
super
|
66
|
-
end
|
67
|
-
alias_method :[]=, :write_attribute
|
68
|
-
|
69
60
|
private
|
70
61
|
|
71
62
|
# Wether or not changes are currently being tracked for this class.
|
data/lib/active_remote/errors.rb
CHANGED
@@ -5,6 +5,9 @@ module ActiveRemote
|
|
5
5
|
class ActiveRemoteError < StandardError
|
6
6
|
end
|
7
7
|
|
8
|
+
class DangerousAttributeError < ActiveRemoteError
|
9
|
+
end
|
10
|
+
|
8
11
|
# Raised by ActiveRemove::Base.save when the remote record is readonly.
|
9
12
|
class ReadOnlyRemoteRecord < ActiveRemoteError
|
10
13
|
end
|
@@ -41,4 +44,7 @@ module ActiveRemote
|
|
41
44
|
# when remote record cannot be saved because it is invalid.
|
42
45
|
class RemoteRecordNotSaved < ActiveRemoteError
|
43
46
|
end
|
47
|
+
|
48
|
+
class UnknownAttributeError < ActiveRemoteError
|
49
|
+
end
|
44
50
|
end
|
@@ -49,8 +49,9 @@ module ActiveRemote
|
|
49
49
|
# Instantiate a record with the given remote attributes. Generally used
|
50
50
|
# when retrieving records that already exist, so @new_record is set to false.
|
51
51
|
#
|
52
|
-
def instantiate(
|
53
|
-
|
52
|
+
def instantiate(new_attributes, options = {})
|
53
|
+
attributes = self.build_from_rpc(new_attributes)
|
54
|
+
new_object = self.allocate.init_with(attributes)
|
54
55
|
new_object.readonly! if options[:readonly]
|
55
56
|
|
56
57
|
new_object
|
@@ -78,7 +79,7 @@ module ActiveRemote
|
|
78
79
|
raise ReadOnlyRemoteRecord if readonly?
|
79
80
|
response = rpc.execute(:delete, scope_key_hash)
|
80
81
|
|
81
|
-
|
82
|
+
add_errors(response.errors) if response.respond_to?(:errors)
|
82
83
|
|
83
84
|
return success? ? freeze : false
|
84
85
|
end
|
@@ -103,7 +104,7 @@ module ActiveRemote
|
|
103
104
|
raise ReadOnlyRemoteRecord if readonly?
|
104
105
|
response = rpc.execute(:destroy, scope_key_hash)
|
105
106
|
|
106
|
-
|
107
|
+
add_errors(response.errors) if response.respond_to?(:errors)
|
107
108
|
|
108
109
|
return success? ? freeze : false
|
109
110
|
end
|
@@ -127,19 +128,9 @@ module ActiveRemote
|
|
127
128
|
# Instantiate a record with the given remote attributes. Generally used
|
128
129
|
# when retrieving records that already exist, so @new_record is set to false.
|
129
130
|
#
|
130
|
-
def instantiate(
|
131
|
-
|
132
|
-
|
133
|
-
end
|
134
|
-
|
135
|
-
# TODO: figure out how to safely run after_find/search callbacks here
|
136
|
-
# currently, several functions use this code path, so an alternate path
|
137
|
-
# may need to be added
|
138
|
-
|
139
|
-
run_callbacks :initialize
|
140
|
-
|
141
|
-
@new_record = false
|
142
|
-
self
|
131
|
+
def instantiate(new_attributes)
|
132
|
+
new_attributes = self.class.build_from_rpc(new_attributes)
|
133
|
+
init_with(new_attributes)
|
143
134
|
end
|
144
135
|
|
145
136
|
# Returns true if the remote record hasn't been saved yet; otherwise,
|
@@ -252,7 +243,7 @@ module ActiveRemote
|
|
252
243
|
response = rpc.execute(:create, new_attributes)
|
253
244
|
|
254
245
|
assign_attributes(response.to_hash)
|
255
|
-
|
246
|
+
add_errors(response.errors) if response.respond_to?(:errors)
|
256
247
|
|
257
248
|
@new_record = has_errors?
|
258
249
|
success?
|
@@ -282,7 +273,7 @@ module ActiveRemote
|
|
282
273
|
response = rpc.execute(:update, updated_attributes)
|
283
274
|
|
284
275
|
assign_attributes(response.to_hash)
|
285
|
-
|
276
|
+
add_errors(response.errors) if response.respond_to?(:errors)
|
286
277
|
|
287
278
|
success?
|
288
279
|
end
|