protobuf-activerecord 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/lib/protoable/convert.rb +75 -0
- data/lib/protoable/errors.rb +23 -0
- data/lib/protoable/fields.rb +116 -0
- data/lib/protoable/inheritable_class_instance_variables.rb +54 -0
- data/lib/protoable/persistence.rb +94 -0
- data/lib/protoable/serialization.rb +120 -0
- data/lib/protoable.rb +15 -0
- data/lib/protobuf/activerecord/version.rb +5 -0
- data/lib/protobuf-activerecord.rb +3 -0
- data/protobuf-activerecord.gemspec +37 -0
- data/spec/protoable/convert_spec.rb +265 -0
- data/spec/protoable/fields_spec.rb +131 -0
- data/spec/protoable/inheritable_class_instance_variables_spec.rb +5 -0
- data/spec/protoable/persistence_spec.rb +158 -0
- data/spec/protoable/serialization_spec.rb +133 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/db/setup.rb +21 -0
- data/spec/support/db.rb +1 -0
- data/spec/support/models/user.rb +36 -0
- data/spec/support/models.rb +1 -0
- data/spec/support/protobuf/user.pb.rb +9 -0
- data/spec/support/protobuf.rb +2 -0
- metadata +253 -0
@@ -0,0 +1,265 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Convert do
|
4
|
+
describe ".convert_int64_to_date" do
|
5
|
+
let(:date) { Date.current }
|
6
|
+
let(:int64) { date.to_time.to_i }
|
7
|
+
|
8
|
+
it "initializes a new Date object from the value" do
|
9
|
+
Timecop.freeze(Date.current) do
|
10
|
+
User.convert_int64_to_date(int64).should eq date
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".convert_int64_to_datetime" do
|
16
|
+
let(:datetime) { DateTime.current }
|
17
|
+
let(:int64) { datetime.to_time.to_i }
|
18
|
+
|
19
|
+
it "initializes a new DateTime object from the value" do
|
20
|
+
Timecop.freeze(DateTime.current) do
|
21
|
+
User.convert_int64_to_datetime(int64).should eq datetime
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ".convert_int64_to_time" do
|
27
|
+
let(:time) { Time.current }
|
28
|
+
let(:int64) { time.to_time.to_i }
|
29
|
+
|
30
|
+
it "initializes a new Time object from the value" do
|
31
|
+
Timecop.freeze(Time.current) do
|
32
|
+
User.convert_int64_to_time(int64).should be_within(1).of(time)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "._protobuf_convert_columns_to_fields" do
|
38
|
+
context "when there is a column converter for the field" do
|
39
|
+
let(:email_value) { "foo@test.co" }
|
40
|
+
let(:email_converter) { User.method(:convert_email_to_lowercase) }
|
41
|
+
|
42
|
+
before { User.stub(:_protobuf_column_converters).and_return({ :email => email_converter }) }
|
43
|
+
|
44
|
+
it "calls the column converter with the given value" do
|
45
|
+
email_converter.should_receive(:call).with(email_value)
|
46
|
+
User._protobuf_convert_columns_to_fields(:email, email_value)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "and it's corresponding column type has a default converter" do
|
50
|
+
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
51
|
+
|
52
|
+
it "calls the column converter with the given value" do
|
53
|
+
email_converter.should_receive(:call).with(email_value)
|
54
|
+
User._protobuf_convert_columns_to_fields(:email, email_value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when the column type is :date" do
|
60
|
+
let(:date) { Date.current }
|
61
|
+
let(:integer) { date.to_time.to_i }
|
62
|
+
|
63
|
+
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
64
|
+
|
65
|
+
it "converts the given value to an integer" do
|
66
|
+
User._protobuf_convert_columns_to_fields(:foo_date, date).should eq integer
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the column type is :datetime" do
|
71
|
+
let(:datetime) { DateTime.current }
|
72
|
+
let(:integer) { datetime.to_time.to_i }
|
73
|
+
|
74
|
+
before { User.stub(:_protobuf_datetime_column?).and_return(true) }
|
75
|
+
|
76
|
+
it "converts the given value to an integer" do
|
77
|
+
User._protobuf_convert_columns_to_fields(:foo_datetime, datetime).should eq integer
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when the column type is :time" do
|
82
|
+
let(:time) { Time.current }
|
83
|
+
let(:integer) { time.to_time.to_i }
|
84
|
+
|
85
|
+
before { User.stub(:_protobuf_time_column?).and_return(true) }
|
86
|
+
|
87
|
+
it "converts the given value to an integer" do
|
88
|
+
User._protobuf_convert_columns_to_fields(:foo_time, time).should eq integer
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when the column type is :timestamp" do
|
93
|
+
let(:timestamp) { Time.current }
|
94
|
+
let(:integer) { timestamp.to_time.to_i }
|
95
|
+
|
96
|
+
before { User.stub(:_protobuf_timestamp_column?).and_return(true) }
|
97
|
+
|
98
|
+
it "converts the given value to an integer" do
|
99
|
+
User._protobuf_convert_columns_to_fields(:foo_timestamp, timestamp).should eq integer
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when no conversion is necessary" do
|
104
|
+
let(:value) { "Foo" }
|
105
|
+
|
106
|
+
it "returns the given value" do
|
107
|
+
User._protobuf_convert_columns_to_fields(:foo, value).should eq value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "._protobuf_convert_fields_to_columns" do
|
113
|
+
let(:email_value) { "foo@test.co" }
|
114
|
+
let(:email_converter) { lambda { |value| value } }
|
115
|
+
|
116
|
+
before { User.stub(:_protobuf_field_converters).and_return({ :email => email_converter }) }
|
117
|
+
|
118
|
+
it "calls the field converter with the given value" do
|
119
|
+
email_converter.should_receive(:call).with(email_value)
|
120
|
+
User._protobuf_convert_fields_to_columns(:email, email_value)
|
121
|
+
end
|
122
|
+
|
123
|
+
context "and it's corresponding column type has a default converter" do
|
124
|
+
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
125
|
+
|
126
|
+
it "calls the field converter with the given value" do
|
127
|
+
email_converter.should_receive(:call).with(email_value)
|
128
|
+
User._protobuf_convert_fields_to_columns(:email, email_value)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "when the given field's corresponding column type is :date" do
|
133
|
+
let(:date) { Date.current }
|
134
|
+
let(:value) { date.to_time.to_i }
|
135
|
+
|
136
|
+
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
137
|
+
|
138
|
+
it "converts the given value to a Date object" do
|
139
|
+
User._protobuf_convert_fields_to_columns(:foo_date, value).should eq date
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when given field's corresponding the column type is :datetime" do
|
144
|
+
let(:datetime) { DateTime.current }
|
145
|
+
let(:value) { datetime.to_i }
|
146
|
+
|
147
|
+
before { User.stub(:_protobuf_datetime_column?).and_return(true) }
|
148
|
+
|
149
|
+
it "converts the given value to a DateTime object" do
|
150
|
+
User._protobuf_convert_fields_to_columns(:foo_datetime, value).should be_a(DateTime)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "converts the given value to a DateTime object of the same value" do
|
154
|
+
User._protobuf_convert_fields_to_columns(:foo_datetime, value).should be_within(1).of(datetime)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "when given field's corresponding the column type is :time" do
|
159
|
+
let(:time) { Time.current }
|
160
|
+
let(:value) { time.to_i }
|
161
|
+
|
162
|
+
before { User.stub(:_protobuf_time_column?).and_return(true) }
|
163
|
+
|
164
|
+
it "converts the given value to a Time object" do
|
165
|
+
User._protobuf_convert_fields_to_columns(:foo_time, value).should be_a(Time)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "converts the given value to a Time object of the same value" do
|
169
|
+
User._protobuf_convert_fields_to_columns(:foo_time, value).should be_within(1).of(time)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when given field's corresponding the column type is :timestamp" do
|
174
|
+
let(:time) { Time.current }
|
175
|
+
let(:value) { time.to_i }
|
176
|
+
|
177
|
+
before { User.stub(:_protobuf_timestamp_column?).and_return(true) }
|
178
|
+
|
179
|
+
it "converts the given value to a Time object" do
|
180
|
+
User._protobuf_convert_fields_to_columns(:foo_time, value).should be_a(Time)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "converts the given value to a Time object of the same value" do
|
184
|
+
User._protobuf_convert_fields_to_columns(:foo_timestamp, value).should be_within(1).of(time)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "when no conversion is necessary" do
|
189
|
+
let(:value) { "Foo" }
|
190
|
+
|
191
|
+
it "returns the given value" do
|
192
|
+
User._protobuf_convert_fields_to_columns(:foo, value).should eq value
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "column type predicates" do
|
198
|
+
before { User.stub(:_protobuf_column_types).and_return(Hash.new) }
|
199
|
+
after { User.unstub(:_protobuf_column_types) }
|
200
|
+
|
201
|
+
describe "._protobuf_date_column?" do
|
202
|
+
before { User._protobuf_column_types[:date] = [ :foo_date ] }
|
203
|
+
|
204
|
+
context "when the column type is :date" do
|
205
|
+
it "is true" do
|
206
|
+
User._protobuf_date_column?(:foo_date).should be_true
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "when the column type is not :date" do
|
211
|
+
it "is false" do
|
212
|
+
User._protobuf_date_column?(:bar_date).should be_false
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "._protobuf_datetime_column?" do
|
218
|
+
before { User._protobuf_column_types[:datetime] = [ :foo_datetime ] }
|
219
|
+
|
220
|
+
context "when the column type is :datetime" do
|
221
|
+
it "is true" do
|
222
|
+
User._protobuf_datetime_column?(:foo_datetime).should be_true
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context "when the column type is not :datetime" do
|
227
|
+
it "is false" do
|
228
|
+
User._protobuf_datetime_column?(:bar_datetime).should be_false
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe "._protobuf_time_column?" do
|
234
|
+
before { User._protobuf_column_types[:time] = [ :foo_time ] }
|
235
|
+
|
236
|
+
context "when the column type is :time" do
|
237
|
+
it "is true" do
|
238
|
+
User._protobuf_time_column?(:foo_time).should be_true
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context "when the column type is not :time" do
|
243
|
+
it "is false" do
|
244
|
+
User._protobuf_time_column?(:bar_time).should be_false
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "._protobuf_timestamp_column?" do
|
250
|
+
before { User._protobuf_column_types[:timestamp] = [ :foo_timestamp ] }
|
251
|
+
|
252
|
+
context "when the column type is :timestamp" do
|
253
|
+
it "is true" do
|
254
|
+
User._protobuf_timestamp_column?(:foo_timestamp).should be_true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context "when the column type is not :timestamp" do
|
259
|
+
it "is false" do
|
260
|
+
User._protobuf_timestamp_column?(:bar_timestamp).should be_false
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Fields 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
|
+
describe ".convert_field" do
|
32
|
+
context "when the given converter is a hash" do
|
33
|
+
let(:method) { User.method(:convert_base64_to_string) }
|
34
|
+
|
35
|
+
before { User.convert_field :public_key, :from => :base64, :to => :string }
|
36
|
+
|
37
|
+
it "determines the method using the hash's :to and :from keys" do
|
38
|
+
User._protobuf_field_converters[:public_key].should eq method
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when the given converter is a symbol" do
|
43
|
+
context "when the converter is not a defined method" do
|
44
|
+
let(:callable) { User.method(:convert_base64_to_string) }
|
45
|
+
|
46
|
+
before { User.convert_field :email, :base64 }
|
47
|
+
|
48
|
+
it "determines the method using the converter as the 'from' and the column type as the 'to'" do
|
49
|
+
User._protobuf_field_converters[:email].should eq callable
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when the converter is a defined method" do
|
54
|
+
let(:callable) { User.method(:convert_email_to_lowercase) }
|
55
|
+
|
56
|
+
before { User.convert_field :email, :convert_email_to_lowercase }
|
57
|
+
|
58
|
+
it "creates a callable method object from the converter" do
|
59
|
+
User._protobuf_field_converters[:email].should eq callable
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when the given converter is nil" do
|
65
|
+
it "raises an exception" do
|
66
|
+
expect { User.convert_field :email, nil }.to raise_exception(Protoable::FieldConverterError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the given converter is not callable" do
|
71
|
+
it "raises an exception" do
|
72
|
+
expect { User.convert_field :email, :foo }.to raise_exception(Protoable::FieldConverterError)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when the given converter is callable" do
|
77
|
+
let(:callable) { lambda { |value| value } }
|
78
|
+
|
79
|
+
before { User.convert_field :email, callable }
|
80
|
+
|
81
|
+
it "adds the given converter to list of field converters" do
|
82
|
+
User._protobuf_field_converters[:email].should eq callable
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe ".transform_column" do
|
88
|
+
context "when the given converter is a symbol" do
|
89
|
+
context "when the converter is not a defined method" do
|
90
|
+
it "raises an exception" do
|
91
|
+
expect { User.transform_column :name, :foo }.to raise_exception(Protoable::ColumnTransformerError)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when the converter is a defined method" do
|
96
|
+
let(:callable) { User.method(:extract_first_name) }
|
97
|
+
|
98
|
+
before { User.transform_column :first_name, :extract_first_name }
|
99
|
+
|
100
|
+
it "creates a callable method object from the converter" do
|
101
|
+
User._protobuf_column_transformers[:first_name].should eq callable
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when the given converter is nil" do
|
107
|
+
it "raises an exception" do
|
108
|
+
expect { User.transform_column :name, nil }.to raise_exception(Protoable::ColumnTransformerError)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when the given converter is not callable" do
|
113
|
+
it "raises an exception" do
|
114
|
+
expect { User.transform_column :name, double(:not_callable) }.to raise_exception(Protoable::ColumnTransformerError)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "when the given transformer is callable" do
|
119
|
+
let(:callable) { lambda { |proto| nil } }
|
120
|
+
|
121
|
+
before {
|
122
|
+
User.stub(:_protobuf_column_transformers).and_return(Hash.new)
|
123
|
+
User.transform_column :account_id, callable
|
124
|
+
}
|
125
|
+
|
126
|
+
it "adds the given converter to the list of protobuf field transformers" do
|
127
|
+
User._protobuf_column_transformers[:account_id] = callable
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Persistence do
|
4
|
+
let(:user) { User.new(user_attributes) }
|
5
|
+
let(:user_attributes) { { :first_name => 'foo', :last_name => 'bar', :email => 'foo@test.co' } }
|
6
|
+
let(:proto_hash) { { :name => 'foo bar', :email => 'foo@test.co' } }
|
7
|
+
let(:proto) { Proto::User.new(proto_hash) }
|
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 column transformers" do
|
27
|
+
User.stub(:_protobuf_column_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 column transformer is defined for the field" 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 column transformer is not defined for the field" do
|
42
|
+
before {
|
43
|
+
User.stub(:_protobuf_convert_fields_to_columns) do |key, value|
|
44
|
+
value
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
it "converts the field value" do
|
49
|
+
attribute_fields = User.attributes_from_proto(proto)
|
50
|
+
attribute_fields.should eq user_attributes
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ".create_from_proto" do
|
56
|
+
it "initializes a new object with attributes from the given protobuf message" do
|
57
|
+
user = User.create_from_proto(proto)
|
58
|
+
attributes = user.attributes.slice("first_name", "last_name", "email")
|
59
|
+
attributes.should eq user_attributes.stringify_keys
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when a block is given" do
|
63
|
+
it "yields to the block with attributes from the given protobuf message" do
|
64
|
+
yielded = nil
|
65
|
+
|
66
|
+
User.create_from_proto(proto) do |attributes|
|
67
|
+
yielded = "boom!"
|
68
|
+
end
|
69
|
+
|
70
|
+
yielded.should eq "boom!"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when the object is valid" do
|
75
|
+
it "saves the record" do
|
76
|
+
user = User.create_from_proto(proto)
|
77
|
+
user.persisted?.should be_true
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when an error occurs while saving" do
|
81
|
+
before { User.any_instance.stub(:save!).and_raise(RuntimeError) }
|
82
|
+
|
83
|
+
it "raises an exception" do
|
84
|
+
expect { User.create_from_proto(proto) }.to raise_exception
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when the object is not valid" do
|
90
|
+
before { User.any_instance.stub(:valid?).and_return(false) }
|
91
|
+
|
92
|
+
it "returns the new object, unsaved" do
|
93
|
+
user = User.create_from_proto(proto)
|
94
|
+
user.persisted?.should be_false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#attributes_from_proto" do
|
100
|
+
it "gets attributes from the given protobuf message" do
|
101
|
+
User.should_receive(:attributes_from_proto).with(proto)
|
102
|
+
user.attributes_from_proto(proto)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#destroy_from_proto" do
|
107
|
+
it "destroys the object" do
|
108
|
+
user.should_receive(:destroy)
|
109
|
+
user.destroy_from_proto
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#update_from_proto" do
|
114
|
+
it "updates the object with attributes from the given protobuf message" do
|
115
|
+
user.should_receive(:assign_attributes).with(user_attributes)
|
116
|
+
user.update_from_proto(proto)
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when a block is given" do
|
120
|
+
it "yields to the block with attributes from the given protobuf message" do
|
121
|
+
yielded = nil
|
122
|
+
|
123
|
+
user.update_from_proto(proto) do |attributes|
|
124
|
+
yielded = "boom!"
|
125
|
+
end
|
126
|
+
|
127
|
+
yielded.should eq "boom!"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "when the object is valid" do
|
132
|
+
before { user.stub(:valid?).and_return(true) }
|
133
|
+
|
134
|
+
context "when saving is successful" do
|
135
|
+
it "returns true" do
|
136
|
+
user.stub(:save!).and_return(true)
|
137
|
+
user.update_from_proto(proto).should be_true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "when an error occurs while saving" do
|
142
|
+
before { user.stub(:save!).and_raise(RuntimeError) }
|
143
|
+
|
144
|
+
it "raises an exception" do
|
145
|
+
expect { user.update_from_proto }.to raise_exception
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "when the object is invalid" do
|
151
|
+
before { user.stub(:valid?).and_return(false) }
|
152
|
+
|
153
|
+
it "returns false" do
|
154
|
+
user.update_from_proto(proto).should be_false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Serialization do
|
4
|
+
let(:protobuf_message) { ::Proto::User }
|
5
|
+
|
6
|
+
describe ".convert_column" do
|
7
|
+
context "when the given converter is a hash" do
|
8
|
+
let(:method) { User.method(:convert_base64_to_string) }
|
9
|
+
|
10
|
+
before { User.convert_column :public_key, :from => :base64, :to => :string }
|
11
|
+
|
12
|
+
it "determines the method using the hash's :to and :from keys" do
|
13
|
+
User._protobuf_column_converters[:public_key].should eq method
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when the given converter is a symbol" do
|
18
|
+
context "when the converter is not a defined method" do
|
19
|
+
let(:callable) { User.method(:convert_base64_to_string) }
|
20
|
+
|
21
|
+
before { User.convert_column :email, :base64 }
|
22
|
+
|
23
|
+
it "determines the method using the converter as the 'from' and the column type as the 'to'" do
|
24
|
+
User._protobuf_column_converters[:email].should eq callable
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when the converter is a defined method" do
|
29
|
+
let(:callable) { User.method(:convert_email_to_lowercase) }
|
30
|
+
|
31
|
+
before { User.convert_column :email, :convert_email_to_lowercase }
|
32
|
+
|
33
|
+
it "creates a callable method object from the converter" do
|
34
|
+
User._protobuf_column_converters[:email].should eq callable
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when the given converter is nil" do
|
40
|
+
it "raises an exception" do
|
41
|
+
expect { User.convert_column :email, nil }.to raise_exception(Protoable::ColumnConverterError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the given converter is not callable" do
|
46
|
+
it "raises an exception" do
|
47
|
+
expect { User.convert_column :email, :foo }.to raise_exception(Protoable::ColumnConverterError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when the given converter is callable" do
|
52
|
+
let(:callable) { lambda { |value| value } }
|
53
|
+
|
54
|
+
before { User.convert_column :email, callable }
|
55
|
+
|
56
|
+
it "adds the given converter to list of column converters" do
|
57
|
+
User._protobuf_column_converters[:email].should eq callable
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe ".protobuf_message" do
|
63
|
+
before { User.protobuf_message(protobuf_message) }
|
64
|
+
|
65
|
+
context "given a value" do
|
66
|
+
let(:protobuf_fields) { [ :name, :email, :tags ] }
|
67
|
+
|
68
|
+
it "sets .protobuf_fields" do
|
69
|
+
User.protobuf_fields.should =~ protobuf_fields
|
70
|
+
end
|
71
|
+
|
72
|
+
it "defines #to_proto" do
|
73
|
+
User.allocate.should respond_to :to_proto
|
74
|
+
end
|
75
|
+
|
76
|
+
it "defines #to_proto_hash" do
|
77
|
+
User.allocate.should respond_to :to_proto_hash
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns the protobuf message for this object" do
|
82
|
+
User.protobuf_message.should eq protobuf_message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when protobuf_message is defined" do
|
87
|
+
let(:attributes) { Hash.new }
|
88
|
+
let(:user) { User.new(attributes) }
|
89
|
+
|
90
|
+
before { User.protobuf_message(protobuf_message) }
|
91
|
+
|
92
|
+
describe "#protoable_attributes" do
|
93
|
+
let(:attributes) {
|
94
|
+
{
|
95
|
+
:first_name => "foo",
|
96
|
+
:last_name => "bar",
|
97
|
+
:email => "foo@test.co"
|
98
|
+
}
|
99
|
+
}
|
100
|
+
let(:protoable_attributes) { { :name => user.name, :email => user.email, :tags => nil } }
|
101
|
+
|
102
|
+
it "returns a hash of protobuf fields that this object has getters for" do
|
103
|
+
user.protoable_attributes.should eq protoable_attributes
|
104
|
+
end
|
105
|
+
|
106
|
+
it "converts attributes values for protobuf messages" do
|
107
|
+
user.should_receive(:_protobuf_convert_columns_to_fields).any_number_of_times
|
108
|
+
user.protoable_attributes
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#to_proto" do
|
113
|
+
let(:proto) { protobuf_message.new(proto_hash) }
|
114
|
+
let(:proto_hash) { { :name => "foo" } }
|
115
|
+
|
116
|
+
before { user.stub(:to_proto_hash).and_return(proto_hash) }
|
117
|
+
|
118
|
+
it "intializes a new protobuf message with attributes from #to_proto_hash" do
|
119
|
+
user.to_proto.should eq proto
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#to_proto_hash" do
|
124
|
+
let(:proto_hash) { { :name => "foo" } }
|
125
|
+
|
126
|
+
before { user.stub(:protoable_attributes).and_return(proto_hash) }
|
127
|
+
|
128
|
+
it "returns #protoable_attributes" do
|
129
|
+
user.to_proto_hash.should eq proto_hash
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/spec/spec_helper.rb
ADDED