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
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@protobuf-activerecord --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Adam Hutchison
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Protobuf ActiveRecord
|
2
|
+
|
3
|
+
Protobuf Active Record provides the ability to create Active Record objects from Protocol Buffer messages and vice versa. It adds methods that allow you to create, update, and destroy Active Record objects from protobuf messages. It also provides methods to serialize Active Record objects to protobuf messages.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'protobuf-activerecord'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install protobuf-activerecord
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Protoable
|
2
|
+
module Convert
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend Protoable::Convert::ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def convert_int64_to_time(int64)
|
9
|
+
Time.at(int64.to_i)
|
10
|
+
end
|
11
|
+
|
12
|
+
def convert_int64_to_date(int64)
|
13
|
+
convert_int64_to_time(int64).to_date
|
14
|
+
end
|
15
|
+
|
16
|
+
def convert_int64_to_datetime(int64)
|
17
|
+
convert_int64_to_time(int64).to_datetime
|
18
|
+
end
|
19
|
+
|
20
|
+
def _protobuf_convert_columns_to_fields(key, value)
|
21
|
+
value = case
|
22
|
+
when _protobuf_column_converters.has_key?(key.to_sym) then
|
23
|
+
_protobuf_column_converters[key.to_sym].call(value)
|
24
|
+
when _protobuf_date_column?(key) then
|
25
|
+
value.to_time.to_i
|
26
|
+
when _protobuf_datetime_column?(key) then
|
27
|
+
value.to_i
|
28
|
+
when _protobuf_time_column?(key) then
|
29
|
+
value.to_i
|
30
|
+
when _protobuf_timestamp_column?(key) then
|
31
|
+
value.to_i
|
32
|
+
else
|
33
|
+
value
|
34
|
+
end
|
35
|
+
|
36
|
+
return value
|
37
|
+
end
|
38
|
+
|
39
|
+
def _protobuf_convert_fields_to_columns(key, value)
|
40
|
+
value = case
|
41
|
+
when _protobuf_field_converters.has_key?(key.to_sym) then
|
42
|
+
_protobuf_field_converters[key.to_sym].call(value)
|
43
|
+
when _protobuf_date_column?(key) then
|
44
|
+
convert_int64_to_date(value)
|
45
|
+
when _protobuf_datetime_column?(key) then
|
46
|
+
convert_int64_to_datetime(value)
|
47
|
+
when _protobuf_time_column?(key) then
|
48
|
+
convert_int64_to_time(value)
|
49
|
+
when _protobuf_timestamp_column?(key) then
|
50
|
+
convert_int64_to_time(value)
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
54
|
+
|
55
|
+
return value
|
56
|
+
end
|
57
|
+
|
58
|
+
def _protobuf_date_column?(key)
|
59
|
+
_protobuf_column_types.fetch(:date, false) && _protobuf_column_types[:date].include?(key)
|
60
|
+
end
|
61
|
+
|
62
|
+
def _protobuf_datetime_column?(key)
|
63
|
+
_protobuf_column_types.fetch(:datetime, false) && _protobuf_column_types[:datetime].include?(key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def _protobuf_time_column?(key)
|
67
|
+
_protobuf_column_types.fetch(:time, false) && _protobuf_column_types[:time].include?(key)
|
68
|
+
end
|
69
|
+
|
70
|
+
def _protobuf_timestamp_column?(key)
|
71
|
+
_protobuf_column_types.fetch(:timestamp, false) && _protobuf_column_types[:timestamp].include?(key)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Protoable
|
2
|
+
|
3
|
+
# = Protoable errors
|
4
|
+
#
|
5
|
+
# Generic Protoable exception class
|
6
|
+
class ProtoableError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# Raised by Protoable.protobuf_column_convert when the convert method
|
10
|
+
# given is not defined, nil, or not callable.
|
11
|
+
class ColumnConverterError < ProtoableError
|
12
|
+
end
|
13
|
+
|
14
|
+
# Raised by Protoable.protobuf_column_transform when the transformer method
|
15
|
+
# given is not defined, nil, or not callable.
|
16
|
+
class ColumnTransformerError < ProtoableError
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised by Protoable.protobuf_field_convert when the convert method
|
20
|
+
# given is not defined, nil, or not callable.
|
21
|
+
class FieldConverterError < ProtoableError
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'protoable/inheritable_class_instance_variables'
|
2
|
+
|
3
|
+
module Protoable
|
4
|
+
module Fields
|
5
|
+
def self.extended(klass)
|
6
|
+
klass.extend Protoable::Fields::ClassMethods
|
7
|
+
klass.__send__(:include, Protoable::InheritableClassInstanceVariables)
|
8
|
+
|
9
|
+
klass.class_eval do
|
10
|
+
class << self
|
11
|
+
attr_accessor :_protobuf_columns, :_protobuf_column_types,
|
12
|
+
:_protobuf_column_transformers, :_protobuf_field_converters
|
13
|
+
end
|
14
|
+
|
15
|
+
@_protobuf_columns = {}
|
16
|
+
@_protobuf_column_types = Hash.new { |h,k| h[k] = [] }
|
17
|
+
@_protobuf_column_transformers = {}
|
18
|
+
@_protobuf_field_converters = {}
|
19
|
+
|
20
|
+
# NOTE: Make sure each inherited object has the database layout
|
21
|
+
inheritable_attributes :_protobuf_columns, :_protobuf_column_types,
|
22
|
+
:_protobuf_field_converters, :_protobuf_column_transformers
|
23
|
+
end
|
24
|
+
|
25
|
+
_protobuf_map_columns(klass)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Map out the columns for future reference on type conversion
|
29
|
+
#
|
30
|
+
def self._protobuf_map_columns(klass)
|
31
|
+
return unless klass.table_exists?
|
32
|
+
klass.columns.map do |column|
|
33
|
+
klass._protobuf_columns[column.name.to_sym] = column
|
34
|
+
klass._protobuf_column_types[column.type.to_sym] << column.name.to_sym
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
# Define a field conversion from protobuf to db. Accepts a callable,
|
40
|
+
# Symbol, or Hash.
|
41
|
+
#
|
42
|
+
# When given a callable, it is directly used to convert the field.
|
43
|
+
#
|
44
|
+
# When a Hash is given, :from and :to keys are expected and expand
|
45
|
+
# to extracting a class method in the format of
|
46
|
+
# "convert_#{from}_to_#{to}".
|
47
|
+
#
|
48
|
+
# When a symbol is given, it extracts the method with the same name,
|
49
|
+
# if any. When method is not available it is assumed as the "from"
|
50
|
+
# data type, and the "to" value is extracted based on the
|
51
|
+
# name of the column.
|
52
|
+
#
|
53
|
+
# Examples:
|
54
|
+
# convert_field :created_at, :int64
|
55
|
+
# convert_field :public_key, method(:extract_public_key_from_proto)
|
56
|
+
# convert_field :public_key, :extract_public_key_from_proto
|
57
|
+
# convert_field :status, lambda { |proto_field| ... }
|
58
|
+
# convert_field :symmetric_key, :base64
|
59
|
+
# convert_field :symmetric_key, :from => :base64, :to => :encoded_string
|
60
|
+
# convert_field :symmetric_key, :from => :base64, :to => :raw_string
|
61
|
+
#
|
62
|
+
def convert_field(field, callable = nil, &blk)
|
63
|
+
callable ||= blk
|
64
|
+
|
65
|
+
if callable.is_a?(Hash)
|
66
|
+
callable = :"convert_#{callable[:from]}_to_#{callable[:to]}"
|
67
|
+
end
|
68
|
+
|
69
|
+
if callable.is_a?(Symbol)
|
70
|
+
unless self.respond_to?(callable)
|
71
|
+
column = _protobuf_columns[field.to_sym]
|
72
|
+
callable = :"convert_#{callable}_to_#{column.try(:type)}"
|
73
|
+
end
|
74
|
+
callable = method(callable) if self.respond_to?(callable)
|
75
|
+
end
|
76
|
+
|
77
|
+
if callable.nil? || !callable.respond_to?(:call)
|
78
|
+
raise FieldConverterError, 'Field converters must be a callable or block!'
|
79
|
+
end
|
80
|
+
|
81
|
+
_protobuf_field_converters[field.to_sym] = callable
|
82
|
+
end
|
83
|
+
|
84
|
+
# Define a column transformation from protobuf to db. Accepts a callable,
|
85
|
+
# or Symbol.
|
86
|
+
#
|
87
|
+
# When given a callable, it is directly used to convert the field.
|
88
|
+
#
|
89
|
+
# When a symbol is given, it extracts the method with the same name.
|
90
|
+
#
|
91
|
+
# The callable or method must accept a single parameter, which is the
|
92
|
+
# proto message.
|
93
|
+
#
|
94
|
+
# Examples:
|
95
|
+
# transform_column :public_key, :extract_public_key_from_proto
|
96
|
+
# transform_column :status, lambda { |proto_field| ... }
|
97
|
+
#
|
98
|
+
def transform_column(field, callable = nil, &blk)
|
99
|
+
callable ||= blk
|
100
|
+
|
101
|
+
if callable.is_a?(Symbol)
|
102
|
+
unless self.respond_to?(callable)
|
103
|
+
raise ColumnTransformerError, "#{callable} is not defined!"
|
104
|
+
end
|
105
|
+
callable = method(callable) if self.respond_to?(callable)
|
106
|
+
end
|
107
|
+
|
108
|
+
if callable.nil? || !callable.respond_to?(:call)
|
109
|
+
raise ColumnTransformerError, 'Protoable casting needs a callable or block!'
|
110
|
+
end
|
111
|
+
|
112
|
+
_protobuf_column_transformers[field.to_sym] = callable
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Protoable
|
4
|
+
module InheritableClassInstanceVariables
|
5
|
+
def self.included(klass)
|
6
|
+
Thread.exclusive do
|
7
|
+
klass.extend(Protoable::InheritableClassInstanceVariables::ClassMethods)
|
8
|
+
|
9
|
+
klass.class_eval do
|
10
|
+
@_inheritable_class_instance_variables = [ :_inheritable_class_instance_variables ]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def inheritable_attributes(*args)
|
17
|
+
Thread.exclusive do
|
18
|
+
args.flatten.compact.uniq.each do |class_instance_variable|
|
19
|
+
unless @_inheritable_class_instance_variables.include?(class_instance_variable)
|
20
|
+
@_inheritable_class_instance_variables << class_instance_variable
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@_inheritable_class_instance_variables.each do |attr_symbol|
|
25
|
+
unless self.respond_to?("#{attr_symbol}")
|
26
|
+
class_eval %Q{
|
27
|
+
class << self; attr_reader :#{attr_symbol}; end
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
unless self.respond_to?("#{attr_symbol}=")
|
32
|
+
class_eval %Q{
|
33
|
+
class << self; attr_writer :#{attr_symbol}; end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
@_inheritable_class_instance_variables
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def inherited(klass)
|
43
|
+
super # ActiveRecord needs the inherited hook to setup fields
|
44
|
+
|
45
|
+
Thread.exclusive do
|
46
|
+
@_inheritable_class_instance_variables.each do |attribute|
|
47
|
+
attr_sym = :"@#{attribute}"
|
48
|
+
klass.instance_variable_set(attr_sym, self.instance_variable_get(attr_sym))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Protoable
|
2
|
+
module Persistence
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend Protoable::Persistence::ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Filters accessible attributes that exist in the given protobuf message's
|
9
|
+
# fields or have column transformers defined for them.
|
10
|
+
#
|
11
|
+
# Returns a hash of attribute fields with their respective values.
|
12
|
+
#
|
13
|
+
def _filter_attribute_fields(proto)
|
14
|
+
fields = proto.to_hash
|
15
|
+
fields.select! { |key, value| proto.has_field?(key) && !proto.get_field_by_name(key).repeated? }
|
16
|
+
|
17
|
+
attributes = self.new.attributes.keys - protected_attributes.to_a
|
18
|
+
|
19
|
+
attribute_fields = attributes.inject({}) do |hash, column_name|
|
20
|
+
symbolized_column = column_name.to_sym
|
21
|
+
|
22
|
+
if fields.has_key?(symbolized_column) ||
|
23
|
+
_protobuf_column_transformers.has_key?(symbolized_column)
|
24
|
+
hash[symbolized_column] = fields[symbolized_column]
|
25
|
+
end
|
26
|
+
|
27
|
+
hash
|
28
|
+
end
|
29
|
+
|
30
|
+
attribute_fields
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a hash of attributes from a given protobuf message.
|
34
|
+
#
|
35
|
+
# It converts and transforms field values using the field converters and
|
36
|
+
# column transformers, ignoring repeated and nil fields.
|
37
|
+
#
|
38
|
+
def attributes_from_proto(proto)
|
39
|
+
attribute_fields = _filter_attribute_fields(proto)
|
40
|
+
|
41
|
+
attributes = attribute_fields.inject({}) do |hash, (key, value)|
|
42
|
+
if _protobuf_column_transformers.has_key?(key)
|
43
|
+
hash[key] = _protobuf_column_transformers[key].call(proto)
|
44
|
+
else
|
45
|
+
hash[key] = _protobuf_convert_fields_to_columns(key, value)
|
46
|
+
end
|
47
|
+
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
|
51
|
+
attributes
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates an object from the given protobuf message, if it's valid. The
|
55
|
+
# newly created object is returned if it was successfully saved or not.
|
56
|
+
#
|
57
|
+
def create_from_proto(proto)
|
58
|
+
attributes = attributes_from_proto(proto)
|
59
|
+
|
60
|
+
yield(attributes) if block_given?
|
61
|
+
|
62
|
+
record = self.new(attributes)
|
63
|
+
|
64
|
+
record.save! if record.valid?
|
65
|
+
return record
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Calls up to the class version of the method.
|
70
|
+
#
|
71
|
+
def attributes_from_proto(proto)
|
72
|
+
self.class.attributes_from_proto(proto)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Destroys the record. Mainly wrapped to provide a consistent API and
|
76
|
+
# a convient way to override protobuf-specific destroy behavior.
|
77
|
+
#
|
78
|
+
def destroy_from_proto
|
79
|
+
destroy
|
80
|
+
end
|
81
|
+
|
82
|
+
# Update a record from a proto message. Accepts an optional block.
|
83
|
+
# If block is given, yields the attributes that would be updated.
|
84
|
+
#
|
85
|
+
def update_from_proto(proto)
|
86
|
+
attributes = attributes_from_proto(proto)
|
87
|
+
|
88
|
+
yield(attributes) if block_given?
|
89
|
+
|
90
|
+
assign_attributes(attributes)
|
91
|
+
return valid? ? save! : false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Protoable
|
2
|
+
module Serialization
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend Protoable::Serialization::ClassMethods
|
5
|
+
klass.__send__(:include, Protoable::InheritableClassInstanceVariables)
|
6
|
+
|
7
|
+
klass.class_eval do
|
8
|
+
class << self
|
9
|
+
attr_accessor :_protobuf_column_converters, :protobuf_fields
|
10
|
+
end
|
11
|
+
|
12
|
+
@_protobuf_column_converters = {}
|
13
|
+
@protobuf_fields = []
|
14
|
+
|
15
|
+
# NOTE: Make sure each inherited object has the database layout
|
16
|
+
inheritable_attributes :protobuf_fields, :_protobuf_column_converters
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Define a column conversion from db to protobuf. Accepts a callable,
|
22
|
+
# Symbol, or Hash.
|
23
|
+
#
|
24
|
+
# When given a callable, it is directly used to convert the field.
|
25
|
+
#
|
26
|
+
# When a Hash is given, :from and :to keys are expected and expand
|
27
|
+
# to extracting a class method in the format of
|
28
|
+
# "convert_#{from}_to_#{to}".
|
29
|
+
#
|
30
|
+
# When a symbol is given, it extracts the method with the same name,
|
31
|
+
# if any. When method is not available it is assumed as the "from"
|
32
|
+
# data type, and the "to" value is extracted based on the
|
33
|
+
# name of the column.
|
34
|
+
#
|
35
|
+
# Examples:
|
36
|
+
# convert_column :created_at, :int64
|
37
|
+
# convert_column :public_key, :extract_public_key_from_proto
|
38
|
+
# convert_column :public_key, method(:extract_public_key_from_proto)
|
39
|
+
# convert_column :status, lambda { |proto_field| ... }
|
40
|
+
# convert_column :symmetric_key, :from => :base64, :to => :raw_string
|
41
|
+
#
|
42
|
+
def convert_column(field, callable = nil, &blk)
|
43
|
+
callable ||= blk
|
44
|
+
|
45
|
+
if callable.is_a?(Hash)
|
46
|
+
callable = :"convert_#{callable[:from]}_to_#{callable[:to]}"
|
47
|
+
end
|
48
|
+
|
49
|
+
if callable.is_a?(Symbol)
|
50
|
+
unless self.respond_to?(callable)
|
51
|
+
column = _protobuf_columns[field.to_sym]
|
52
|
+
callable = :"convert_#{callable}_to_#{column.try(:type)}"
|
53
|
+
end
|
54
|
+
callable = method(callable) if self.respond_to?(callable)
|
55
|
+
end
|
56
|
+
|
57
|
+
if callable.nil? || !callable.respond_to?(:call)
|
58
|
+
raise ColumnConverterError, 'Column converters must be a callable or block!'
|
59
|
+
end
|
60
|
+
|
61
|
+
_protobuf_column_converters[field.to_sym] = callable
|
62
|
+
end
|
63
|
+
|
64
|
+
# Define the protobuf message class that should be used to serialize the
|
65
|
+
# object to protobuf. Accepts a string or symbol.
|
66
|
+
#
|
67
|
+
# When protobuf_message is declared, Protoable automatically extracts the
|
68
|
+
# fields from the message and automatically adds to_proto and to_proto_hash
|
69
|
+
# methods that serialize the object to protobuf.
|
70
|
+
#
|
71
|
+
# Examples:
|
72
|
+
# protobuf_message :user_message
|
73
|
+
# protobuf_message "UserMessage"
|
74
|
+
# protobuf_message "Namespaced::UserMessage"
|
75
|
+
#
|
76
|
+
def protobuf_message(message = nil)
|
77
|
+
unless message.nil?
|
78
|
+
@_protobuf_message = message.to_s.classify.constantize
|
79
|
+
|
80
|
+
self.protobuf_fields = @_protobuf_message.fields.compact.map do |field|
|
81
|
+
field.name.to_sym
|
82
|
+
end
|
83
|
+
|
84
|
+
define_method(:to_proto) do
|
85
|
+
self.class.protobuf_message.new(self.to_proto_hash)
|
86
|
+
end
|
87
|
+
|
88
|
+
define_method(:to_proto_hash) do
|
89
|
+
protoable_attributes
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@_protobuf_message
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Extracts attributes that correspond to fields on the specified protobuf
|
98
|
+
# message, performing any necessary column conversions on them.
|
99
|
+
#
|
100
|
+
def protoable_attributes
|
101
|
+
protoable_attributes = protobuf_fields.inject({}) do |hash, field|
|
102
|
+
value = respond_to?(field) ? __send__(field) : nil
|
103
|
+
hash[field] = _protobuf_convert_columns_to_fields(field, value)
|
104
|
+
hash
|
105
|
+
end
|
106
|
+
|
107
|
+
protoable_attributes
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def _protobuf_convert_columns_to_fields(field, value)
|
113
|
+
self.class._protobuf_convert_columns_to_fields(field, value)
|
114
|
+
end
|
115
|
+
|
116
|
+
def protobuf_fields
|
117
|
+
self.class.protobuf_fields
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/protoable.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'protoable/convert'
|
2
|
+
require 'protoable/errors'
|
3
|
+
require 'protoable/fields'
|
4
|
+
require 'protoable/persistence'
|
5
|
+
require 'protoable/serialization'
|
6
|
+
|
7
|
+
module Protoable
|
8
|
+
def self.included(klass)
|
9
|
+
klass.extend Protoable::Fields
|
10
|
+
|
11
|
+
klass.__send__(:include, Protoable::Convert)
|
12
|
+
klass.__send__(:include, Protoable::Persistence)
|
13
|
+
klass.__send__(:include, Protoable::Serialization)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'protobuf/activerecord/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "protobuf-activerecord"
|
8
|
+
gem.version = Protobuf::ActiveRecord::VERSION
|
9
|
+
gem.authors = ["Adam Hutchison"]
|
10
|
+
gem.email = ["liveh2o@gmail.com"]
|
11
|
+
gem.homepage = "http://github.com/liveh2o/protobuf-activerecord"
|
12
|
+
gem.summary = %q{Google Protocol Buffers integration for Active Record}
|
13
|
+
gem.description = %q{Provides the ability to create Active Record objects from Protocol Buffer messages and vice versa.}
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
##
|
21
|
+
# Dependencies
|
22
|
+
#
|
23
|
+
gem.add_dependency "activerecord"
|
24
|
+
gem.add_dependency "protobuf", ">= 2.0"
|
25
|
+
|
26
|
+
##
|
27
|
+
# Development dependencies
|
28
|
+
#
|
29
|
+
gem.add_development_dependency "rake"
|
30
|
+
gem.add_development_dependency "geminabox"
|
31
|
+
gem.add_development_dependency "rspec"
|
32
|
+
gem.add_development_dependency "rspec-pride"
|
33
|
+
gem.add_development_dependency "pry-nav"
|
34
|
+
gem.add_development_dependency "simplecov"
|
35
|
+
gem.add_development_dependency "sqlite3"
|
36
|
+
gem.add_development_dependency "timecop"
|
37
|
+
end
|