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.
@@ -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