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
@@ -3,6 +3,60 @@ require 'spec_helper'
|
|
3
3
|
describe Protoable::Serialization do
|
4
4
|
let(:protobuf_message) { UserMessage }
|
5
5
|
|
6
|
+
describe "._protobuf_convert_attributes_to_fields" do
|
7
|
+
context "when the column type is :date" do
|
8
|
+
let(:date) { Date.current }
|
9
|
+
let(:integer) { date.to_time.to_i }
|
10
|
+
|
11
|
+
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
12
|
+
|
13
|
+
it "converts the given value to an integer" do
|
14
|
+
User._protobuf_convert_attributes_to_fields(:foo_date, date).should eq integer
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when the column type is :datetime" do
|
19
|
+
let(:datetime) { DateTime.current }
|
20
|
+
let(:integer) { datetime.to_time.to_i }
|
21
|
+
|
22
|
+
before { User.stub(:_protobuf_datetime_column?).and_return(true) }
|
23
|
+
|
24
|
+
it "converts the given value to an integer" do
|
25
|
+
User._protobuf_convert_attributes_to_fields(:foo_datetime, datetime).should eq integer
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when the column type is :time" do
|
30
|
+
let(:time) { Time.current }
|
31
|
+
let(:integer) { time.to_time.to_i }
|
32
|
+
|
33
|
+
before { User.stub(:_protobuf_time_column?).and_return(true) }
|
34
|
+
|
35
|
+
it "converts the given value to an integer" do
|
36
|
+
User._protobuf_convert_attributes_to_fields(:foo_time, time).should eq integer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when the column type is :timestamp" do
|
41
|
+
let(:timestamp) { Time.current }
|
42
|
+
let(:integer) { timestamp.to_time.to_i }
|
43
|
+
|
44
|
+
before { User.stub(:_protobuf_timestamp_column?).and_return(true) }
|
45
|
+
|
46
|
+
it "converts the given value to an integer" do
|
47
|
+
User._protobuf_convert_attributes_to_fields(:foo_timestamp, timestamp).should eq integer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when no conversion is necessary" do
|
52
|
+
let(:value) { "Foo" }
|
53
|
+
|
54
|
+
it "returns the given value" do
|
55
|
+
User._protobuf_convert_attributes_to_fields(:foo, value).should eq value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
6
60
|
describe ".field_from_record" do
|
7
61
|
context "when the given converter is a symbol" do
|
8
62
|
let(:callable) { lambda { |value| User.__send__(:extract_first_name) } }
|
@@ -35,87 +89,117 @@ describe Protoable::Serialization do
|
|
35
89
|
end
|
36
90
|
end
|
37
91
|
|
38
|
-
describe ".
|
39
|
-
|
40
|
-
let(:method) { lambda { |value| User.__send__(:convert_base64_to_string, value) } }
|
92
|
+
describe ".protobuf_message" do
|
93
|
+
let(:options) { { :only => [] } }
|
41
94
|
|
42
|
-
|
95
|
+
before { User.protobuf_message(protobuf_message, options) }
|
96
|
+
after { User.protobuf_message(protobuf_message, {}) }
|
43
97
|
|
44
|
-
|
45
|
-
|
46
|
-
User.
|
98
|
+
context "given a value" do
|
99
|
+
it "defines #to_proto" do
|
100
|
+
User.allocate.should respond_to :to_proto
|
47
101
|
end
|
48
102
|
end
|
49
103
|
|
50
|
-
context "
|
51
|
-
|
104
|
+
context "given options" do
|
105
|
+
it "merges them with protobuf field options" do
|
106
|
+
User._protobuf_field_options.should eq options
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns the protobuf message for this object" do
|
111
|
+
User.protobuf_message.should eq protobuf_message
|
112
|
+
end
|
113
|
+
end
|
52
114
|
|
53
|
-
|
115
|
+
context "when protobuf_message is defined" do
|
116
|
+
let(:attributes) { Hash.new }
|
117
|
+
let(:user) { User.new(attributes) }
|
54
118
|
|
55
|
-
|
56
|
-
|
57
|
-
|
119
|
+
before { User.protobuf_message(protobuf_message) }
|
120
|
+
|
121
|
+
describe "#_filter_field_attributes" do
|
122
|
+
context "when options has :only" do
|
123
|
+
it "only returns the given field(s)" do
|
124
|
+
fields = user._filter_field_attributes(:only => :name).should
|
125
|
+
fields.should eq [ :name ]
|
126
|
+
end
|
58
127
|
end
|
59
|
-
end
|
60
128
|
|
61
|
-
|
62
|
-
|
63
|
-
|
129
|
+
context "when options has :except" do
|
130
|
+
it "returns all except the given field(s)" do
|
131
|
+
fields = user._filter_field_attributes(:except => :name).should
|
132
|
+
fields.should eq [ :guid, :email, :email_domain ]
|
133
|
+
end
|
64
134
|
end
|
65
135
|
end
|
66
136
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
137
|
+
describe "#_filtered_fields" do
|
138
|
+
it "returns protobuf fields" do
|
139
|
+
user._filtered_fields.should eq [ :guid, :name, :email, :email_domain ]
|
140
|
+
end
|
71
141
|
|
72
|
-
|
73
|
-
|
142
|
+
context "given :deprecated => false" do
|
143
|
+
it "filters all deprecated fields" do
|
144
|
+
fields = user._filtered_fields(:deprecated => false).should
|
145
|
+
fields.should eq [ :guid, :name, :email ]
|
146
|
+
end
|
74
147
|
end
|
75
148
|
end
|
76
|
-
end
|
77
149
|
|
78
|
-
|
79
|
-
|
150
|
+
describe "#_normalize_options" do
|
151
|
+
let(:options) { { :only => [ :name ] } }
|
80
152
|
|
81
|
-
|
82
|
-
|
153
|
+
context "given empty options" do
|
154
|
+
before { User.protobuf_message(protobuf_message, options) }
|
83
155
|
|
84
|
-
|
85
|
-
|
156
|
+
it "returns the class's protobuf field options" do
|
157
|
+
User.allocate._normalize_options({}).should eq options
|
158
|
+
end
|
86
159
|
end
|
87
160
|
|
88
|
-
|
89
|
-
User.
|
161
|
+
context "given options" do
|
162
|
+
before { User.protobuf_message(protobuf_message, {}) }
|
163
|
+
|
164
|
+
it "merges them with the class's protobuf field options" do
|
165
|
+
normalized_options = User.allocate._normalize_options(options)
|
166
|
+
normalized_options[:only].should eq options[:only]
|
167
|
+
end
|
90
168
|
end
|
91
169
|
|
92
|
-
|
93
|
-
User.
|
170
|
+
context "given options with :only" do
|
171
|
+
before { User.protobuf_message(protobuf_message, {}) }
|
172
|
+
|
173
|
+
it "ensures that :except exists" do
|
174
|
+
normalized_options = User.allocate._normalize_options(options)
|
175
|
+
normalized_options[:except].should eq []
|
176
|
+
end
|
94
177
|
end
|
95
|
-
end
|
96
178
|
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
end
|
179
|
+
context "given options with :except" do
|
180
|
+
let(:options) { { :except => [ :name ] } }
|
101
181
|
|
102
|
-
|
103
|
-
let(:attributes) { Hash.new }
|
104
|
-
let(:user) { User.new(attributes) }
|
182
|
+
before { User.protobuf_message(protobuf_message, {}) }
|
105
183
|
|
106
|
-
|
184
|
+
it "ensures that :only exists" do
|
185
|
+
normalized_options = User.allocate._normalize_options(options)
|
186
|
+
normalized_options[:only].should eq []
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
107
190
|
|
108
|
-
describe "#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
:email => "foo@test.co"
|
116
|
-
}
|
191
|
+
describe "#fields_from_record" do
|
192
|
+
let(:attributes) {
|
193
|
+
{
|
194
|
+
:guid => "foo",
|
195
|
+
:first_name => "bar",
|
196
|
+
:last_name => "baz",
|
197
|
+
:email => "foo@test.co"
|
117
198
|
}
|
118
|
-
|
199
|
+
}
|
200
|
+
|
201
|
+
context "when a transformer is defined for the field" do
|
202
|
+
let(:fields_from_record) { { :guid => user.guid, :name => user.name, :email => user.email, :email_domain => 'test.co' } }
|
119
203
|
let(:transformer) { { :email_domain => lambda { |record| record.email.split('@').last } } }
|
120
204
|
|
121
205
|
before {
|
@@ -123,28 +207,27 @@ describe Protoable::Serialization do
|
|
123
207
|
}
|
124
208
|
|
125
209
|
it "gets the field from the transformer" do
|
126
|
-
user.
|
210
|
+
user.fields_from_record.should eq fields_from_record
|
127
211
|
end
|
128
212
|
end
|
129
213
|
|
130
214
|
context "when a transformer is not defined for the field" do
|
131
|
-
let(:
|
132
|
-
{
|
133
|
-
:guid => "foo",
|
134
|
-
:first_name => "bar",
|
135
|
-
:last_name => "baz",
|
136
|
-
:email => "foo@test.co"
|
137
|
-
}
|
138
|
-
}
|
139
|
-
let(:protoable_attributes) { { :guid => user.guid, :name => user.name, :email => user.email, :email_domain => nil } }
|
215
|
+
let(:fields_from_record) { { :guid => user.guid, :name => user.name, :email => user.email, :email_domain => nil } }
|
140
216
|
|
141
217
|
it "returns a hash of protobuf fields that this object has getters for" do
|
142
|
-
user.
|
218
|
+
user.fields_from_record.should eq fields_from_record
|
143
219
|
end
|
144
220
|
|
145
221
|
it "converts attributes values for protobuf messages" do
|
146
222
|
user.should_receive(:_protobuf_convert_attributes_to_fields).any_number_of_times
|
147
|
-
user.
|
223
|
+
user.fields_from_record
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "given options with :include" do
|
228
|
+
it "adds the given field to the list of serialized fields" do
|
229
|
+
fields = user.fields_from_record(:include => :token)
|
230
|
+
fields.include?(:token).should be_true
|
148
231
|
end
|
149
232
|
end
|
150
233
|
end
|
@@ -153,21 +236,11 @@ describe Protoable::Serialization do
|
|
153
236
|
let(:proto) { protobuf_message.new(proto_hash) }
|
154
237
|
let(:proto_hash) { { :name => "foo" } }
|
155
238
|
|
156
|
-
before { user.stub(:
|
239
|
+
before { user.stub(:fields_from_record).and_return(proto_hash) }
|
157
240
|
|
158
241
|
it "intializes a new protobuf message with attributes from #to_proto_hash" do
|
159
242
|
user.to_proto.should eq proto
|
160
243
|
end
|
161
244
|
end
|
162
|
-
|
163
|
-
describe "#to_proto_hash" do
|
164
|
-
let(:proto_hash) { { :name => "foo" } }
|
165
|
-
|
166
|
-
before { user.stub(:protoable_attributes).and_return(proto_hash) }
|
167
|
-
|
168
|
-
it "returns #protoable_attributes" do
|
169
|
-
user.to_proto_hash.should eq proto_hash
|
170
|
-
end
|
171
|
-
end
|
172
245
|
end
|
173
246
|
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Transformation 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) { UserMessage.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 "includes attributes that aren't fields, but have attribute transformers" do
|
21
|
+
User.stub(:_protobuf_attribute_transformers).and_return({ :account_id => :fetch_account_id })
|
22
|
+
attribute_fields = User._filter_attribute_fields(proto)
|
23
|
+
attribute_fields.has_key?(:account_id).should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "._protobuf_convert_fields_to_columns" do
|
28
|
+
context "when the given field's corresponding column type is :date" do
|
29
|
+
let(:date) { Date.current }
|
30
|
+
let(:value) { date.to_time.to_i }
|
31
|
+
|
32
|
+
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
33
|
+
|
34
|
+
it "converts the given value to a Date object" do
|
35
|
+
User._protobuf_convert_fields_to_columns(:foo_date, value).should eq date
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when given field's corresponding the column type is :datetime" do
|
40
|
+
let(:datetime) { DateTime.current }
|
41
|
+
let(:value) { datetime.to_i }
|
42
|
+
|
43
|
+
before { User.stub(:_protobuf_datetime_column?).and_return(true) }
|
44
|
+
|
45
|
+
it "converts the given value to a DateTime object" do
|
46
|
+
User._protobuf_convert_fields_to_columns(:foo_datetime, value).should be_a(DateTime)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "converts the given value to a DateTime object of the same value" do
|
50
|
+
User._protobuf_convert_fields_to_columns(:foo_datetime, value).should be_within(1).of(datetime)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when given field's corresponding the column type is :time" do
|
55
|
+
let(:time) { Time.current }
|
56
|
+
let(:value) { time.to_i }
|
57
|
+
|
58
|
+
before { User.stub(:_protobuf_time_column?).and_return(true) }
|
59
|
+
|
60
|
+
it "converts the given value to a Time object" do
|
61
|
+
User._protobuf_convert_fields_to_columns(:foo_time, value).should be_a(Time)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "converts the given value to a Time object of the same value" do
|
65
|
+
User._protobuf_convert_fields_to_columns(:foo_time, value).should be_within(1).of(time)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when given field's corresponding the column type is :timestamp" do
|
70
|
+
let(:time) { Time.current }
|
71
|
+
let(:value) { time.to_i }
|
72
|
+
|
73
|
+
before { User.stub(:_protobuf_timestamp_column?).and_return(true) }
|
74
|
+
|
75
|
+
it "converts the given value to a Time object" do
|
76
|
+
User._protobuf_convert_fields_to_columns(:foo_time, value).should be_a(Time)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "converts the given value to a Time object of the same value" do
|
80
|
+
User._protobuf_convert_fields_to_columns(:foo_timestamp, value).should be_within(1).of(time)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when no conversion is necessary" do
|
85
|
+
let(:value) { "Foo" }
|
86
|
+
|
87
|
+
it "returns the given value" do
|
88
|
+
User._protobuf_convert_fields_to_columns(:foo, value).should eq value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe ".attributes_from_proto" do
|
94
|
+
context "when a transformer is defined for the attribute" do
|
95
|
+
it "transforms the field value" do
|
96
|
+
attribute_fields = User.attributes_from_proto(proto)
|
97
|
+
attribute_fields[:first_name].should eq user_attributes[:first_name]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "when a transformer is a callable that returns nil" do
|
102
|
+
before do
|
103
|
+
transformers = User._protobuf_attribute_transformers
|
104
|
+
User.stub(:_protobuf_attribute_transformers).and_return(
|
105
|
+
{:account_id => lambda { |proto| nil }}.merge(transformers)
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "does not set the attribute" do
|
110
|
+
attribute_fields = User.attributes_from_proto(proto)
|
111
|
+
attribute_fields.should eq user_attributes
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when a transformer is a callable that returns a value" do
|
116
|
+
before do
|
117
|
+
transformers = User._protobuf_attribute_transformers
|
118
|
+
User.stub(:_protobuf_attribute_transformers).and_return(
|
119
|
+
{:account_id => lambda { |proto| 1 }}.merge(transformers)
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "sets the attribute" do
|
124
|
+
attribute_fields = User.attributes_from_proto(proto)
|
125
|
+
attribute_fields.should eq user_attributes.merge(:account_id => 1)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "when a transformer is not defined for the attribute" do
|
130
|
+
before {
|
131
|
+
User.stub(:_protobuf_convert_fields_to_columns) do |key, value|
|
132
|
+
value
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
it "converts the field value" do
|
137
|
+
attribute_fields = User.attributes_from_proto(proto)
|
138
|
+
attribute_fields.should eq user_attributes
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe ".attribute_from_proto" do
|
144
|
+
context "when the given transformer is a symbol" do
|
145
|
+
let(:callable) { lambda { |value| User.__send__(:extract_first_name) } }
|
146
|
+
|
147
|
+
before { User.attribute_from_proto :first_name, :extract_first_name }
|
148
|
+
|
149
|
+
it "creates a callable method object from the converter" do
|
150
|
+
User.should_receive(:extract_first_name)
|
151
|
+
User._protobuf_attribute_transformers[:first_name].call(1)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "when the given transformer is not callable" do
|
156
|
+
it "raises an exception" do
|
157
|
+
expect { User.attribute_from_proto :name, nil }.to raise_exception(Protoable::AttributeTransformerError)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "when the given transformer is callable" do
|
162
|
+
let(:callable) { lambda { |proto| nil } }
|
163
|
+
|
164
|
+
before {
|
165
|
+
User.stub(:_protobuf_attribute_transformers).and_return(Hash.new)
|
166
|
+
User.attribute_from_proto :account_id, callable
|
167
|
+
}
|
168
|
+
|
169
|
+
it "adds the given converter to the list of protobuf field transformers" do
|
170
|
+
User._protobuf_attribute_transformers[:account_id] = callable
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe ".convert_int64_to_date" do
|
176
|
+
let(:date) { Date.current }
|
177
|
+
let(:int64) { date.to_time.to_i }
|
178
|
+
|
179
|
+
it "initializes a new Date object from the value" do
|
180
|
+
Timecop.freeze(Date.current) do
|
181
|
+
User.convert_int64_to_date(int64).should eq date
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe ".convert_int64_to_datetime" do
|
187
|
+
let(:datetime) { DateTime.current }
|
188
|
+
let(:int64) { datetime.to_time.to_i }
|
189
|
+
|
190
|
+
it "initializes a new DateTime object from the value" do
|
191
|
+
Timecop.freeze(DateTime.current) do
|
192
|
+
User.convert_int64_to_datetime(int64).should eq datetime
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe ".convert_int64_to_time" do
|
198
|
+
let(:time) { Time.current }
|
199
|
+
let(:int64) { time.to_time.to_i }
|
200
|
+
|
201
|
+
it "initializes a new Time object from the value" do
|
202
|
+
Timecop.freeze(Time.current) do
|
203
|
+
User.convert_int64_to_time(int64).should be_within(1).of(time)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#attributes_from_proto" do
|
209
|
+
it "gets attributes from the given protobuf message" do
|
210
|
+
User.should_receive(:attributes_from_proto).with(proto)
|
211
|
+
user.attributes_from_proto(proto)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|