protobuf-activerecord 1.2.6 → 2.0.0.beta
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/Gemfile +0 -4
- data/lib/protobuf/activerecord/protoable.rb +4 -4
- data/lib/protobuf/activerecord/protoable/columns.rb +56 -0
- data/lib/protobuf/activerecord/protoable/errors.rb +0 -10
- data/lib/protobuf/activerecord/protoable/persistence.rb +0 -91
- data/lib/protobuf/activerecord/protoable/scope.rb +0 -2
- data/lib/protobuf/activerecord/protoable/serialization.rb +87 -64
- data/lib/protobuf/activerecord/protoable/transformation.rb +140 -0
- data/lib/protobuf/activerecord/version.rb +1 -1
- data/protobuf-activerecord.gemspec +1 -1
- data/spec/protoable/columns_spec.rb +99 -0
- data/spec/protoable/persistence_spec.rb +2 -180
- data/spec/protoable/serialization_spec.rb +149 -76
- data/spec/protoable/transformation_spec.rb +214 -0
- data/spec/support/definitions/user.proto +1 -1
- data/spec/support/models/user.rb +4 -8
- data/spec/support/protobuf/user.pb.rb +1 -1
- metadata +15 -18
- data/lib/protobuf/activerecord/protoable/convert.rb +0 -79
- data/lib/protobuf/activerecord/protoable/fields.rb +0 -109
- data/spec/protoable/convert_spec.rb +0 -265
- data/spec/protoable/fields_spec.rb +0 -102
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'heredity/inheritable_class_instance_variables'
|
2
|
+
|
3
|
+
module Protoable
|
4
|
+
module Transformation
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend Protoable::Transformation::ClassMethods
|
7
|
+
klass.__send__(:include, ::Heredity::InheritableClassInstanceVariables)
|
8
|
+
|
9
|
+
klass.class_eval do
|
10
|
+
class << self
|
11
|
+
attr_accessor :_protobuf_attribute_transformers
|
12
|
+
end
|
13
|
+
|
14
|
+
@_protobuf_attribute_transformers = {}
|
15
|
+
|
16
|
+
inheritable_attributes :_protobuf_attribute_transformers
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Filters accessible attributes that exist in the given protobuf message's
|
22
|
+
# fields or have attribute transformers defined for them.
|
23
|
+
#
|
24
|
+
# Returns a hash of attribute fields with their respective values.
|
25
|
+
#
|
26
|
+
# :nodoc:
|
27
|
+
def _filter_attribute_fields(proto)
|
28
|
+
fields = proto.to_hash
|
29
|
+
fields.select! { |key, value| proto.has_field?(key) && !proto.get_field_by_name(key).repeated? }
|
30
|
+
|
31
|
+
attribute_fields = attribute_names.inject({}) do |hash, column_name|
|
32
|
+
symbolized_column = column_name.to_sym
|
33
|
+
|
34
|
+
if fields.has_key?(symbolized_column) ||
|
35
|
+
_protobuf_attribute_transformers.has_key?(symbolized_column)
|
36
|
+
hash[symbolized_column] = fields[symbolized_column]
|
37
|
+
end
|
38
|
+
|
39
|
+
hash
|
40
|
+
end
|
41
|
+
|
42
|
+
attribute_fields
|
43
|
+
end
|
44
|
+
|
45
|
+
# :nodoc:
|
46
|
+
def _protobuf_convert_fields_to_columns(key, value)
|
47
|
+
return value if value.nil?
|
48
|
+
|
49
|
+
value = case
|
50
|
+
when _protobuf_date_column?(key) then
|
51
|
+
convert_int64_to_date(value)
|
52
|
+
when _protobuf_datetime_column?(key) then
|
53
|
+
convert_int64_to_datetime(value)
|
54
|
+
when _protobuf_time_column?(key) then
|
55
|
+
convert_int64_to_time(value)
|
56
|
+
when _protobuf_timestamp_column?(key) then
|
57
|
+
convert_int64_to_time(value)
|
58
|
+
else
|
59
|
+
value
|
60
|
+
end
|
61
|
+
|
62
|
+
return value
|
63
|
+
end
|
64
|
+
|
65
|
+
# Define an attribute transformation from protobuf. Accepts a Symbol,
|
66
|
+
# callable, or block.
|
67
|
+
#
|
68
|
+
# When given a callable or block, it is directly used to convert the field.
|
69
|
+
#
|
70
|
+
# When a symbol is given, it extracts the method with the same name.
|
71
|
+
#
|
72
|
+
# The callable or method must accept a single parameter, which is the
|
73
|
+
# proto message.
|
74
|
+
#
|
75
|
+
# Examples:
|
76
|
+
# attribute_from_proto :public_key, :extract_public_key_from_proto
|
77
|
+
# attribute_from_proto :status, lambda { |proto| # Do some stuff... }
|
78
|
+
# attribute_from_proto :status do |proto|
|
79
|
+
# # Do some blocky stuff...
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
def attribute_from_proto(attribute, transformer = nil, &block)
|
83
|
+
transformer ||= block
|
84
|
+
|
85
|
+
if transformer.is_a?(Symbol)
|
86
|
+
callable = lambda { |value| self.__send__(transformer, value) }
|
87
|
+
else
|
88
|
+
callable = transformer
|
89
|
+
end
|
90
|
+
|
91
|
+
unless callable.respond_to?(:call)
|
92
|
+
raise AttributeTransformerError, 'Attribute transformers need a callable or block!'
|
93
|
+
end
|
94
|
+
|
95
|
+
_protobuf_attribute_transformers[attribute.to_sym] = callable
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Creates a hash of attributes from a given protobuf message.
|
100
|
+
#
|
101
|
+
# It converts and transforms field values using the field converters and
|
102
|
+
# attribute transformers, ignoring repeated and nil fields.
|
103
|
+
#
|
104
|
+
def attributes_from_proto(proto)
|
105
|
+
attribute_fields = _filter_attribute_fields(proto)
|
106
|
+
|
107
|
+
attributes = attribute_fields.inject({}) do |hash, (key, value)|
|
108
|
+
if _protobuf_attribute_transformers.has_key?(key)
|
109
|
+
attribute = _protobuf_attribute_transformers[key].call(proto)
|
110
|
+
hash[key] = attribute unless attribute.nil?
|
111
|
+
else
|
112
|
+
hash[key] = _protobuf_convert_fields_to_columns(key, value)
|
113
|
+
end
|
114
|
+
|
115
|
+
hash
|
116
|
+
end
|
117
|
+
|
118
|
+
attributes
|
119
|
+
end
|
120
|
+
|
121
|
+
def convert_int64_to_time(int64)
|
122
|
+
Time.at(int64.to_i)
|
123
|
+
end
|
124
|
+
|
125
|
+
def convert_int64_to_date(int64)
|
126
|
+
convert_int64_to_time(int64).utc.to_date
|
127
|
+
end
|
128
|
+
|
129
|
+
def convert_int64_to_datetime(int64)
|
130
|
+
convert_int64_to_time(int64).to_datetime
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Calls up to the class version of the method.
|
135
|
+
#
|
136
|
+
def attributes_from_proto(proto)
|
137
|
+
self.class.attributes_from_proto(proto)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Columns do
|
4
|
+
describe "._protobuf_map_columns" do
|
5
|
+
context "when the class has a table" do
|
6
|
+
let(:expected_column_names) {
|
7
|
+
User.columns.inject({}) do |hash, column|
|
8
|
+
hash[column.name.to_sym] = column
|
9
|
+
hash
|
10
|
+
end
|
11
|
+
}
|
12
|
+
|
13
|
+
let(:expected_column_types) {
|
14
|
+
User.columns.inject({}) do |hash, column|
|
15
|
+
hash[column.type.to_sym] ||= []
|
16
|
+
hash[column.type.to_sym] << column.name.to_sym
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
}
|
20
|
+
|
21
|
+
it "maps columns by name" do
|
22
|
+
User._protobuf_columns.should eq expected_column_names
|
23
|
+
end
|
24
|
+
|
25
|
+
it "maps column names by column type" do
|
26
|
+
User._protobuf_column_types.should eq expected_column_types
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "column type predicates" do
|
32
|
+
before { User.stub(:_protobuf_column_types).and_return(Hash.new) }
|
33
|
+
after { User.unstub(:_protobuf_column_types) }
|
34
|
+
|
35
|
+
describe "._protobuf_date_column?" do
|
36
|
+
before { User._protobuf_column_types[:date] = [ :foo_date ] }
|
37
|
+
|
38
|
+
context "when the column type is :date" do
|
39
|
+
it "is true" do
|
40
|
+
User._protobuf_date_column?(:foo_date).should be_true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when the column type is not :date" do
|
45
|
+
it "is false" do
|
46
|
+
User._protobuf_date_column?(:bar_date).should be_false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "._protobuf_datetime_column?" do
|
52
|
+
before { User._protobuf_column_types[:datetime] = [ :foo_datetime ] }
|
53
|
+
|
54
|
+
context "when the column type is :datetime" do
|
55
|
+
it "is true" do
|
56
|
+
User._protobuf_datetime_column?(:foo_datetime).should be_true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the column type is not :datetime" do
|
61
|
+
it "is false" do
|
62
|
+
User._protobuf_datetime_column?(:bar_datetime).should be_false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "._protobuf_time_column?" do
|
68
|
+
before { User._protobuf_column_types[:time] = [ :foo_time ] }
|
69
|
+
|
70
|
+
context "when the column type is :time" do
|
71
|
+
it "is true" do
|
72
|
+
User._protobuf_time_column?(:foo_time).should be_true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when the column type is not :time" do
|
77
|
+
it "is false" do
|
78
|
+
User._protobuf_time_column?(:bar_time).should be_false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "._protobuf_timestamp_column?" do
|
84
|
+
before { User._protobuf_column_types[:timestamp] = [ :foo_timestamp ] }
|
85
|
+
|
86
|
+
context "when the column type is :timestamp" do
|
87
|
+
it "is true" do
|
88
|
+
User._protobuf_timestamp_column?(:foo_timestamp).should be_true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when the column type is not :timestamp" do
|
93
|
+
it "is false" do
|
94
|
+
User._protobuf_timestamp_column?(:bar_timestamp).should be_false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -6,80 +6,6 @@ describe Protoable::Persistence do
|
|
6
6
|
let(:proto_hash) { { :name => 'foo bar', :email => 'foo@test.co' } }
|
7
7
|
let(:proto) { UserMessage.new(proto_hash) }
|
8
8
|
|
9
|
-
describe "._filter_attribute_fields" do
|
10
|
-
it "includes fields that have values" do
|
11
|
-
attribute_fields = User._filter_attribute_fields(proto)
|
12
|
-
attribute_fields[:email].should eq proto_hash[:email]
|
13
|
-
end
|
14
|
-
|
15
|
-
it "filters repeated fields" do
|
16
|
-
attribute_fields = User._filter_attribute_fields(proto)
|
17
|
-
attribute_fields.has_key?(:tags).should be_false
|
18
|
-
end
|
19
|
-
|
20
|
-
it "filters fields that map to protected attributes" do
|
21
|
-
User.stub(:protected_attributes).and_return([ "email" ])
|
22
|
-
attribute_fields = User._filter_attribute_fields(proto)
|
23
|
-
attribute_fields.has_key?(:email).should be_false
|
24
|
-
end
|
25
|
-
|
26
|
-
it "includes attributes that aren't fields, but have attribute transformers" do
|
27
|
-
User.stub(:_protobuf_attribute_transformers).and_return({ :account_id => :fetch_account_id })
|
28
|
-
attribute_fields = User._filter_attribute_fields(proto)
|
29
|
-
attribute_fields.has_key?(:account_id).should be_true
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
describe ".attributes_from_proto" do
|
34
|
-
context "when a transformer is defined for the attribute" do
|
35
|
-
it "transforms the field value" do
|
36
|
-
attribute_fields = User.attributes_from_proto(proto)
|
37
|
-
attribute_fields[:first_name].should eq user_attributes[:first_name]
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
context "when a transformer is a callable that returns nil" do
|
42
|
-
before do
|
43
|
-
transformers = User._protobuf_attribute_transformers
|
44
|
-
User.stub(:_protobuf_attribute_transformers).and_return(
|
45
|
-
{:account_id => lambda { |proto| nil }}.merge(transformers)
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
it "does not set the attribute" do
|
50
|
-
attribute_fields = User.attributes_from_proto(proto)
|
51
|
-
attribute_fields.should eq user_attributes
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
context "when a transformer is a callable that returns a value" do
|
56
|
-
before do
|
57
|
-
transformers = User._protobuf_attribute_transformers
|
58
|
-
User.stub(:_protobuf_attribute_transformers).and_return(
|
59
|
-
{:account_id => lambda { |proto| 1 }}.merge(transformers)
|
60
|
-
)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "sets the attribute" do
|
64
|
-
attribute_fields = User.attributes_from_proto(proto)
|
65
|
-
attribute_fields.should eq user_attributes.merge(:account_id => 1)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
context "when a transformer is not defined for the attribute" do
|
70
|
-
before {
|
71
|
-
User.stub(:_protobuf_convert_fields_to_columns) do |key, value|
|
72
|
-
value
|
73
|
-
end
|
74
|
-
}
|
75
|
-
|
76
|
-
it "converts the field value" do
|
77
|
-
attribute_fields = User.attributes_from_proto(proto)
|
78
|
-
attribute_fields.should eq user_attributes
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
9
|
describe ".create" do
|
84
10
|
it "accepts a protobuf message" do
|
85
11
|
User.any_instance.should_receive(:save)
|
@@ -104,65 +30,7 @@ describe Protoable::Persistence do
|
|
104
30
|
end
|
105
31
|
end
|
106
32
|
|
107
|
-
describe "
|
108
|
-
it "initializes a new object with attributes from the given protobuf message" do
|
109
|
-
user = User.create_from_proto(proto)
|
110
|
-
attributes = user.attributes.slice("first_name", "last_name", "email")
|
111
|
-
attributes.should eq user_attributes.stringify_keys
|
112
|
-
end
|
113
|
-
|
114
|
-
context "when a block is given" do
|
115
|
-
it "yields to the block with attributes from the given protobuf message" do
|
116
|
-
yielded = nil
|
117
|
-
|
118
|
-
User.create_from_proto(proto) do |attributes|
|
119
|
-
yielded = "boom!"
|
120
|
-
end
|
121
|
-
|
122
|
-
yielded.should eq "boom!"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
context "when the object is valid" do
|
127
|
-
it "saves the record" do
|
128
|
-
user = User.create_from_proto(proto)
|
129
|
-
user.persisted?.should be_true
|
130
|
-
end
|
131
|
-
|
132
|
-
context "when an error occurs while saving" do
|
133
|
-
before { User.any_instance.stub(:create).and_raise(RuntimeError) }
|
134
|
-
|
135
|
-
it "raises an exception" do
|
136
|
-
expect { User.create_from_proto(proto) }.to raise_exception
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
context "when the object is not valid" do
|
142
|
-
before { User.any_instance.stub(:valid?).and_return(false) }
|
143
|
-
|
144
|
-
it "returns the new object, unsaved" do
|
145
|
-
user = User.create_from_proto(proto)
|
146
|
-
user.persisted?.should be_false
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
describe "#attributes_from_proto" do
|
152
|
-
it "gets attributes from the given protobuf message" do
|
153
|
-
User.should_receive(:attributes_from_proto).with(proto)
|
154
|
-
user.attributes_from_proto(proto)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
describe "#destroy_from_proto" do
|
159
|
-
it "destroys the object" do
|
160
|
-
user.should_receive(:destroy)
|
161
|
-
user.destroy_from_proto
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
describe ".update_attributes" do
|
33
|
+
describe "#update_attributes" do
|
166
34
|
it "accepts a protobuf message" do
|
167
35
|
User.any_instance.should_receive(:save)
|
168
36
|
user.update_attributes(proto)
|
@@ -174,7 +42,7 @@ describe Protoable::Persistence do
|
|
174
42
|
end
|
175
43
|
end
|
176
44
|
|
177
|
-
describe "
|
45
|
+
describe "#update_attributes!" do
|
178
46
|
it "accepts a protobuf message" do
|
179
47
|
User.any_instance.should_receive(:save!)
|
180
48
|
user.update_attributes!(proto)
|
@@ -185,50 +53,4 @@ describe Protoable::Persistence do
|
|
185
53
|
user.update_attributes!(user_attributes)
|
186
54
|
end
|
187
55
|
end
|
188
|
-
|
189
|
-
describe "#update_from_proto" do
|
190
|
-
it "updates the object with attributes from the given protobuf message" do
|
191
|
-
user.should_receive(:assign_attributes).with(user_attributes, {})
|
192
|
-
user.update_from_proto(proto)
|
193
|
-
end
|
194
|
-
|
195
|
-
context "when a block is given" do
|
196
|
-
it "yields to the block with attributes from the given protobuf message" do
|
197
|
-
yielded = nil
|
198
|
-
|
199
|
-
user.update_from_proto(proto) do |attributes|
|
200
|
-
yielded = "boom!"
|
201
|
-
end
|
202
|
-
|
203
|
-
yielded.should eq "boom!"
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
context "when the object is valid" do
|
208
|
-
before { user.stub(:valid?).and_return(true) }
|
209
|
-
|
210
|
-
context "when saving is successful" do
|
211
|
-
it "returns true" do
|
212
|
-
user.stub(:assign_attributes).and_return(true)
|
213
|
-
user.update_from_proto(proto).should be_true
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
context "when an error occurs while saving" do
|
218
|
-
before { user.stub(:assign_attributes).and_raise(RuntimeError) }
|
219
|
-
|
220
|
-
it "raises an exception" do
|
221
|
-
expect { user.update_from_proto }.to raise_exception
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
context "when the object is invalid" do
|
227
|
-
before { user.stub(:valid?).and_return(false) }
|
228
|
-
|
229
|
-
it "returns false" do
|
230
|
-
user.update_from_proto(proto).should be_false
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
56
|
end
|