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
data/Gemfile
CHANGED
@@ -6,7 +6,3 @@ gemspec
|
|
6
6
|
gem 'builder', '~> 3.0.4' # Builder 3.1.x is not supported by Active Record
|
7
7
|
# and Bundler has trouble resolving the dependency
|
8
8
|
# with Geminabox.
|
9
|
-
|
10
|
-
gem 'timecop', '~> 0.3.5', :group => :development # Timecop 0.4.x is has a bug
|
11
|
-
# when dealing with timezones
|
12
|
-
# on Time objects.
|
@@ -1,17 +1,17 @@
|
|
1
|
-
require 'protobuf/activerecord/protoable/
|
1
|
+
require 'protobuf/activerecord/protoable/columns'
|
2
2
|
require 'protobuf/activerecord/protoable/errors'
|
3
|
-
require 'protobuf/activerecord/protoable/fields'
|
4
3
|
require 'protobuf/activerecord/protoable/persistence'
|
5
4
|
require 'protobuf/activerecord/protoable/scope'
|
6
5
|
require 'protobuf/activerecord/protoable/serialization'
|
6
|
+
require 'protobuf/activerecord/protoable/transformation'
|
7
7
|
|
8
8
|
module Protoable
|
9
9
|
def self.included(klass)
|
10
|
-
klass.extend Protoable::Fields
|
11
10
|
klass.extend Protoable::Scope
|
12
11
|
|
13
|
-
klass.__send__(:include, Protoable::
|
12
|
+
klass.__send__(:include, Protoable::Columns)
|
14
13
|
klass.__send__(:include, Protoable::Persistence)
|
15
14
|
klass.__send__(:include, Protoable::Serialization)
|
15
|
+
klass.__send__(:include, Protoable::Transformation)
|
16
16
|
end
|
17
17
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'heredity/inheritable_class_instance_variables'
|
2
|
+
|
3
|
+
module Protoable
|
4
|
+
module Columns
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend Protoable::Columns::ClassMethods
|
7
|
+
klass.__send__(:include, ::Heredity::InheritableClassInstanceVariables)
|
8
|
+
|
9
|
+
klass.class_eval do
|
10
|
+
class << self
|
11
|
+
attr_accessor :_protobuf_columns, :_protobuf_column_types
|
12
|
+
end
|
13
|
+
|
14
|
+
@_protobuf_columns = {}
|
15
|
+
@_protobuf_column_types = Hash.new { |h,k| h[k] = [] }
|
16
|
+
|
17
|
+
# NOTE: Make sure each inherited object has the database layout
|
18
|
+
inheritable_attributes :_protobuf_columns, :_protobuf_column_types
|
19
|
+
end
|
20
|
+
|
21
|
+
_protobuf_map_columns(klass)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Map out the columns for future reference on type conversion
|
25
|
+
#
|
26
|
+
def self._protobuf_map_columns(klass)
|
27
|
+
return unless klass.table_exists?
|
28
|
+
klass.columns.map do |column|
|
29
|
+
klass._protobuf_columns[column.name.to_sym] = column
|
30
|
+
klass._protobuf_column_types[column.type.to_sym] << column.name.to_sym
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
# :nodoc:
|
36
|
+
def _protobuf_date_column?(key)
|
37
|
+
_protobuf_column_types.fetch(:date, false) && _protobuf_column_types[:date].include?(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# :nodoc:
|
41
|
+
def _protobuf_datetime_column?(key)
|
42
|
+
_protobuf_column_types.fetch(:datetime, false) && _protobuf_column_types[:datetime].include?(key)
|
43
|
+
end
|
44
|
+
|
45
|
+
# :nodoc:
|
46
|
+
def _protobuf_time_column?(key)
|
47
|
+
_protobuf_column_types.fetch(:time, false) && _protobuf_column_types[:time].include?(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# :nodoc:
|
51
|
+
def _protobuf_timestamp_column?(key)
|
52
|
+
_protobuf_column_types.fetch(:timestamp, false) && _protobuf_column_types[:timestamp].include?(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -6,21 +6,11 @@ module Protoable
|
|
6
6
|
class ProtoableError < StandardError
|
7
7
|
end
|
8
8
|
|
9
|
-
# Raised by Protoable.protoable_attribute when the convert method given is
|
10
|
-
# not callable.
|
11
|
-
class AttributeConverterError < ProtoableError
|
12
|
-
end
|
13
|
-
|
14
9
|
# Raised by Protoable.attribute_from_proto when the transformer method
|
15
10
|
# given is not callable.
|
16
11
|
class AttributeTransformerError < ProtoableError
|
17
12
|
end
|
18
13
|
|
19
|
-
# Raised by Protoable.convert_field when the convert method
|
20
|
-
# given not callable.
|
21
|
-
class FieldConverterError < ProtoableError
|
22
|
-
end
|
23
|
-
|
24
14
|
# Raised by Protoable.field_from_record when the convert method
|
25
15
|
# given not callable.
|
26
16
|
class FieldTransformerError < ProtoableError
|
@@ -17,62 +17,6 @@ module Protoable
|
|
17
17
|
end
|
18
18
|
|
19
19
|
module ClassMethods
|
20
|
-
# Filters accessible attributes that exist in the given protobuf message's
|
21
|
-
# fields or have attribute transformers defined for them.
|
22
|
-
#
|
23
|
-
# Returns a hash of attribute fields with their respective values.
|
24
|
-
#
|
25
|
-
def _filter_attribute_fields(proto)
|
26
|
-
fields = proto.to_hash
|
27
|
-
fields.select! { |key, value| proto.has_field?(key) && !proto.get_field_by_name(key).repeated? }
|
28
|
-
|
29
|
-
attribute_fields = _filtered_attributes.inject({}) do |hash, column_name|
|
30
|
-
symbolized_column = column_name.to_sym
|
31
|
-
|
32
|
-
if fields.has_key?(symbolized_column) ||
|
33
|
-
_protobuf_attribute_transformers.has_key?(symbolized_column)
|
34
|
-
hash[symbolized_column] = fields[symbolized_column]
|
35
|
-
end
|
36
|
-
|
37
|
-
hash
|
38
|
-
end
|
39
|
-
|
40
|
-
attribute_fields
|
41
|
-
end
|
42
|
-
|
43
|
-
# Filters protected attributes from the available attributes list. When
|
44
|
-
# set through accessible attributes, returns the accessible attributes.
|
45
|
-
# When set through protected attributes, returns the attributes minus any
|
46
|
-
# protected attributes.
|
47
|
-
#
|
48
|
-
def _filtered_attributes
|
49
|
-
return accessible_attributes.to_a if accessible_attributes.present?
|
50
|
-
|
51
|
-
return self.attribute_names - protected_attributes.to_a
|
52
|
-
end
|
53
|
-
|
54
|
-
# Creates a hash of attributes from a given protobuf message.
|
55
|
-
#
|
56
|
-
# It converts and transforms field values using the field converters and
|
57
|
-
# attribute transformers, ignoring repeated and nil fields.
|
58
|
-
#
|
59
|
-
def attributes_from_proto(proto)
|
60
|
-
attribute_fields = _filter_attribute_fields(proto)
|
61
|
-
|
62
|
-
attributes = attribute_fields.inject({}) do |hash, (key, value)|
|
63
|
-
if _protobuf_attribute_transformers.has_key?(key)
|
64
|
-
attribute = _protobuf_attribute_transformers[key].call(proto)
|
65
|
-
hash[key] = attribute unless attribute.nil?
|
66
|
-
else
|
67
|
-
hash[key] = _protobuf_convert_fields_to_columns(key, value)
|
68
|
-
end
|
69
|
-
|
70
|
-
hash
|
71
|
-
end
|
72
|
-
|
73
|
-
attributes
|
74
|
-
end
|
75
|
-
|
76
20
|
# :nodoc:
|
77
21
|
def create(attributes, options = {}, &block)
|
78
22
|
attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
|
@@ -86,17 +30,6 @@ module Protoable
|
|
86
30
|
|
87
31
|
super(attributes, options)
|
88
32
|
end
|
89
|
-
|
90
|
-
# Creates an object from the given protobuf message, if it's valid. The
|
91
|
-
# newly created object is returned if it was successfully saved or not.
|
92
|
-
#
|
93
|
-
def create_from_proto(proto, options = {})
|
94
|
-
attributes = attributes_from_proto(proto)
|
95
|
-
|
96
|
-
yield(attributes) if block_given?
|
97
|
-
|
98
|
-
self.create(attributes, options)
|
99
|
-
end
|
100
33
|
end
|
101
34
|
|
102
35
|
# :nodoc:
|
@@ -106,19 +39,6 @@ module Protoable
|
|
106
39
|
super(attributes, options)
|
107
40
|
end
|
108
41
|
|
109
|
-
# Calls up to the class version of the method.
|
110
|
-
#
|
111
|
-
def attributes_from_proto(proto)
|
112
|
-
self.class.attributes_from_proto(proto)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Destroys the record. Mainly wrapped to provide a consistent API and
|
116
|
-
# a convient way to override protobuf-specific destroy behavior.
|
117
|
-
#
|
118
|
-
def destroy_from_proto
|
119
|
-
destroy
|
120
|
-
end
|
121
|
-
|
122
42
|
# :nodoc:
|
123
43
|
def update_attributes(attributes, options = {})
|
124
44
|
attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
|
@@ -132,16 +52,5 @@ module Protoable
|
|
132
52
|
|
133
53
|
super(attributes, options)
|
134
54
|
end
|
135
|
-
|
136
|
-
# Update a record from a proto message. Accepts an optional block.
|
137
|
-
# If block is given, yields the attributes that would be updated.
|
138
|
-
#
|
139
|
-
def update_from_proto(proto, options = {})
|
140
|
-
attributes = attributes_from_proto(proto)
|
141
|
-
|
142
|
-
yield(attributes) if block_given?
|
143
|
-
|
144
|
-
update_attributes(attributes, options)
|
145
|
-
end
|
146
55
|
end
|
147
56
|
end
|
@@ -4,7 +4,6 @@ module Protoable
|
|
4
4
|
klass.class_eval do
|
5
5
|
class << self
|
6
6
|
alias_method :by_fields, :search_scope
|
7
|
-
alias_method :from_proto, :search_scope
|
8
7
|
alias_method :scope_from_proto, :search_scope
|
9
8
|
end
|
10
9
|
end
|
@@ -34,7 +33,6 @@ module Protoable
|
|
34
33
|
# # Search starting with the default scope and searchable fields
|
35
34
|
# User.search_scope(request)
|
36
35
|
# User.by_fields(request)
|
37
|
-
# User.from_proto(request)
|
38
36
|
# User.scope_from_proto(request)
|
39
37
|
#
|
40
38
|
def search_scope(proto)
|
@@ -8,20 +8,38 @@ module Protoable
|
|
8
8
|
|
9
9
|
klass.class_eval do
|
10
10
|
class << self
|
11
|
-
attr_accessor :
|
12
|
-
:_protobuf_field_transformers, :protobuf_fields
|
11
|
+
attr_accessor :_protobuf_field_transformers, :_protobuf_field_options
|
13
12
|
end
|
14
13
|
|
15
|
-
@_protobuf_attribute_converters = {}
|
16
14
|
@_protobuf_field_transformers = {}
|
17
|
-
@
|
15
|
+
@_protobuf_field_options = {}
|
18
16
|
|
19
|
-
inheritable_attributes :
|
20
|
-
:
|
17
|
+
inheritable_attributes :_protobuf_field_transformers, :_protobuf_field_options,
|
18
|
+
:protobuf_message
|
21
19
|
end
|
22
20
|
end
|
23
21
|
|
24
22
|
module ClassMethods
|
23
|
+
# :nodoc:
|
24
|
+
def _protobuf_convert_attributes_to_fields(key, value)
|
25
|
+
return value if value.nil?
|
26
|
+
|
27
|
+
value = case
|
28
|
+
when _protobuf_date_column?(key) then
|
29
|
+
value.to_time.to_i
|
30
|
+
when _protobuf_datetime_column?(key) then
|
31
|
+
value.to_i
|
32
|
+
when _protobuf_time_column?(key) then
|
33
|
+
value.to_i
|
34
|
+
when _protobuf_timestamp_column?(key) then
|
35
|
+
value.to_i
|
36
|
+
else
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
return value
|
41
|
+
end
|
42
|
+
|
25
43
|
# Define a field transformation from a record. Accepts a Symbol,
|
26
44
|
# callable, or block that is called with the record being serialized.
|
27
45
|
#
|
@@ -39,8 +57,8 @@ module Protoable
|
|
39
57
|
# # Do some blocky stuff...
|
40
58
|
# end
|
41
59
|
#
|
42
|
-
def field_from_record(field, transformer = nil, &
|
43
|
-
transformer ||=
|
60
|
+
def field_from_record(field, transformer = nil, &block)
|
61
|
+
transformer ||= block
|
44
62
|
|
45
63
|
if transformer.is_a?(Symbol)
|
46
64
|
callable = lambda { |value| self.__send__(transformer, value) }
|
@@ -55,68 +73,37 @@ module Protoable
|
|
55
73
|
_protobuf_field_transformers[field.to_sym] = callable
|
56
74
|
end
|
57
75
|
|
58
|
-
# Define a custom attribute conversion for serialization to protobuf.
|
59
|
-
# Accepts a Symbol, Hash, callable or block.
|
60
|
-
#
|
61
|
-
# When given a callable or block, it is directly used to convert the field.
|
62
|
-
#
|
63
|
-
# When a Hash is given, :from and :to keys are expected and expand
|
64
|
-
# to extracting a class method in the format of
|
65
|
-
# "convert_#{from}_to_#{to}".
|
66
|
-
#
|
67
|
-
# When a symbol is given, it extracts the method with the same name.
|
68
|
-
#
|
69
|
-
# Examples:
|
70
|
-
# protoable_attribute :public_key, :extract_public_key_from_proto
|
71
|
-
# protoable_attribute :symmetric_key, :from => :base64, :to => :raw_string
|
72
|
-
# protoable_attribute :status, lambda { |proto_field| # Do stuff... }
|
73
|
-
# protoable_attribute :status do |proto_field|
|
74
|
-
# # Do some blocky stuff...
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
def protoable_attribute(field, converter = nil, &blk)
|
78
|
-
converter ||= blk
|
79
|
-
converter = :"convert_#{converter[:from]}_to_#{converter[:to]}" if converter.is_a?(Hash)
|
80
|
-
|
81
|
-
if converter.is_a?(Symbol)
|
82
|
-
callable = lambda { |value| __send__(converter, value) }
|
83
|
-
else
|
84
|
-
callable = converter
|
85
|
-
end
|
86
|
-
|
87
|
-
unless callable.respond_to?(:call)
|
88
|
-
raise AttributeConverterError, 'Attribute converters must be a callable or block!'
|
89
|
-
end
|
90
|
-
|
91
|
-
_protobuf_attribute_converters[field.to_sym] = callable
|
92
|
-
end
|
93
|
-
|
94
76
|
# Define the protobuf message class that should be used to serialize the
|
95
|
-
# object to protobuf. Accepts a string or symbol.
|
77
|
+
# object to protobuf. Accepts a string or symbol and an options hash.
|
96
78
|
#
|
97
79
|
# When protobuf_message is declared, Protoable automatically extracts the
|
98
|
-
# fields from the message and automatically adds to_proto
|
99
|
-
#
|
80
|
+
# fields from the message and automatically adds a to_proto method that
|
81
|
+
# serializes the object to protobuf.
|
82
|
+
#
|
83
|
+
# The fields that will be automatically serialized can be configured by
|
84
|
+
# passing :only or :except in the options hash. If :only is specified, only
|
85
|
+
# the specified fields will be serialized. If :except is specified, all
|
86
|
+
# field except the specified fields will be serialized.
|
87
|
+
#
|
88
|
+
# By default, deprecated fields will be serialized. To exclude deprecated
|
89
|
+
# fields, pass :deprecated => false in the options hash.
|
100
90
|
#
|
101
91
|
# Examples:
|
102
92
|
# protobuf_message :user_message
|
103
93
|
# protobuf_message "UserMessage"
|
104
94
|
# protobuf_message "Namespaced::UserMessage"
|
95
|
+
# protobuf_message :user_message, :only => :guid, :name
|
96
|
+
# protobuf_message :user_message, :except => :email_domain
|
97
|
+
# protobuf_message :user_message, :except => :email_domain, :deprecated => false
|
105
98
|
#
|
106
|
-
def protobuf_message(message = nil)
|
99
|
+
def protobuf_message(message = nil, options = {})
|
107
100
|
unless message.nil?
|
108
101
|
@protobuf_message = message.to_s.classify.constantize
|
109
102
|
|
110
|
-
self.
|
111
|
-
field.name.to_sym
|
112
|
-
end
|
113
|
-
|
114
|
-
define_method(:to_proto) do
|
115
|
-
self.class.protobuf_message.new(self.to_proto_hash)
|
116
|
-
end
|
103
|
+
self._protobuf_field_options = options
|
117
104
|
|
118
|
-
define_method(:
|
119
|
-
|
105
|
+
define_method(:to_proto) do |options = {}|
|
106
|
+
self.class.protobuf_message.new(self.fields_from_record(options))
|
120
107
|
end
|
121
108
|
end
|
122
109
|
|
@@ -124,11 +111,51 @@ module Protoable
|
|
124
111
|
end
|
125
112
|
end
|
126
113
|
|
114
|
+
# :nodoc:
|
115
|
+
def _filter_field_attributes(options = {})
|
116
|
+
options = _normalize_options(options)
|
117
|
+
|
118
|
+
fields = _filtered_fields(options)
|
119
|
+
fields &= [ options[:only] ].flatten if options[:only].present?
|
120
|
+
fields -= [ options[:except] ].flatten if options[:except].present?
|
121
|
+
|
122
|
+
fields
|
123
|
+
end
|
124
|
+
|
125
|
+
# :nodoc:
|
126
|
+
def _filtered_fields(options = {})
|
127
|
+
exclude_deprecated = ! options.fetch(:deprecated, true)
|
128
|
+
|
129
|
+
fields = self.class.protobuf_message.fields.map do |field|
|
130
|
+
next if field.nil?
|
131
|
+
next if exclude_deprecated && field.deprecated?
|
132
|
+
field.name.to_sym
|
133
|
+
end
|
134
|
+
fields.compact!
|
135
|
+
|
136
|
+
fields
|
137
|
+
end
|
138
|
+
|
139
|
+
# :nodoc:
|
140
|
+
def _normalize_options(options)
|
141
|
+
options ||= {}
|
142
|
+
options[:only] ||= [] if options.fetch(:except, false)
|
143
|
+
options[:except] ||= [] if options.fetch(:only, false)
|
144
|
+
|
145
|
+
self.class._protobuf_field_options.merge(options)
|
146
|
+
end
|
147
|
+
|
127
148
|
# Extracts attributes that correspond to fields on the specified protobuf
|
128
149
|
# message, performing any necessary column conversions on them.
|
129
150
|
#
|
130
|
-
def
|
131
|
-
|
151
|
+
def fields_from_record(options = {})
|
152
|
+
field_attributes = _filter_field_attributes(options)
|
153
|
+
field_attributes += [ options.fetch(:include, []) ]
|
154
|
+
field_attributes.flatten!
|
155
|
+
field_attributes.compact
|
156
|
+
field_attributes.uniq!
|
157
|
+
|
158
|
+
field_attributes = field_attributes.inject({}) do |hash, field|
|
132
159
|
if _protobuf_field_transformers.has_key?(field)
|
133
160
|
hash[field] = _protobuf_field_transformers[field].call(self)
|
134
161
|
else
|
@@ -138,7 +165,7 @@ module Protoable
|
|
138
165
|
hash
|
139
166
|
end
|
140
167
|
|
141
|
-
|
168
|
+
field_attributes
|
142
169
|
end
|
143
170
|
|
144
171
|
private
|
@@ -150,9 +177,5 @@ module Protoable
|
|
150
177
|
def _protobuf_field_transformers
|
151
178
|
self.class._protobuf_field_transformers
|
152
179
|
end
|
153
|
-
|
154
|
-
def protobuf_fields
|
155
|
-
self.class.protobuf_fields
|
156
|
-
end
|
157
180
|
end
|
158
181
|
end
|