protobuf-activerecord 1.2.6 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Protobuf
2
2
  module ActiveRecord
3
- VERSION = "1.2.6"
3
+ VERSION = "2.0.0.beta"
4
4
  end
5
5
  end
@@ -22,7 +22,7 @@ Gem::Specification.new do |gem|
22
22
  #
23
23
  gem.add_dependency "activerecord"
24
24
  gem.add_dependency "heredity"
25
- gem.add_dependency "protobuf", ">= 2.0"
25
+ gem.add_dependency "protobuf", ">= 2.1.3"
26
26
 
27
27
  ##
28
28
  # Development dependencies
@@ -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 ".create_from_proto" do
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 ".update_attributes!" do
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