protobuf-activerecord 2.1.0 → 3.0.0.pre
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.
- checksums.yaml +6 -14
- data/Gemfile +0 -4
- data/lib/protobuf-activerecord.rb +26 -3
- data/lib/protobuf/active_record/attribute_methods.rb +35 -0
- data/lib/protobuf/active_record/columns.rb +74 -0
- data/lib/protobuf/active_record/config.rb +11 -0
- data/lib/protobuf/active_record/errors.rb +23 -0
- data/lib/protobuf/active_record/mass_assignment_security.rb +16 -0
- data/lib/protobuf/active_record/mass_assignment_security/persistence.rb +57 -0
- data/lib/protobuf/active_record/mass_assignment_security/transformation.rb +27 -0
- data/lib/protobuf/active_record/model.rb +32 -0
- data/lib/protobuf/active_record/persistence.rb +55 -0
- data/lib/protobuf/active_record/railtie.rb +11 -0
- data/lib/protobuf/active_record/scope.rb +121 -0
- data/lib/protobuf/active_record/serialization.rb +194 -0
- data/lib/protobuf/active_record/transformation.rb +155 -0
- data/lib/protobuf/active_record/validations.rb +43 -0
- data/lib/protobuf/{activerecord → active_record}/version.rb +1 -1
- data/protobuf-activerecord.gemspec +4 -5
- data/spec/{protoable → protobuf/active_record}/columns_spec.rb +2 -2
- data/spec/{protoable → protobuf/active_record}/persistence_spec.rb +1 -1
- data/spec/{protoable → protobuf/active_record}/scope_spec.rb +1 -8
- data/spec/{protoable → protobuf/active_record}/serialization_spec.rb +2 -2
- data/spec/{protoable → protobuf/active_record}/transformation_spec.rb +12 -12
- data/spec/support/models/user.rb +1 -1
- metadata +59 -68
- data/lib/protobuf/activerecord/protoable.rb +0 -19
- data/lib/protobuf/activerecord/protoable/columns.rb +0 -56
- data/lib/protobuf/activerecord/protoable/errors.rb +0 -22
- data/lib/protobuf/activerecord/protoable/persistence.rb +0 -56
- data/lib/protobuf/activerecord/protoable/scope.rb +0 -121
- data/lib/protobuf/activerecord/protoable/serialization.rb +0 -190
- data/lib/protobuf/activerecord/protoable/transformation.rb +0 -158
- data/lib/protobuf/activerecord/protoable/validations.rb +0 -39
- data/lib/protobuf/activerecord/protobuf_ext/message.rb +0 -16
@@ -0,0 +1,194 @@
|
|
1
|
+
'active_support/concerns'
|
2
|
+
'heredity'
|
3
|
+
|
4
|
+
module Protobuf
|
5
|
+
module ActiveRecord
|
6
|
+
module Serialization
|
7
|
+
extend ::ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include ::Heredity
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :_protobuf_field_transformers, :_protobuf_field_options
|
14
|
+
end
|
15
|
+
|
16
|
+
@_protobuf_field_transformers = {}
|
17
|
+
@_protobuf_field_options = {}
|
18
|
+
|
19
|
+
inheritable_attributes :_protobuf_field_transformers,
|
20
|
+
:_protobuf_field_options,
|
21
|
+
:protobuf_message
|
22
|
+
|
23
|
+
private :_protobuf_convert_attributes_to_fields
|
24
|
+
private :_protobuf_field_transformers
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
# :nodoc:
|
29
|
+
def _protobuf_convert_attributes_to_fields(key, value)
|
30
|
+
return value if value.nil?
|
31
|
+
|
32
|
+
value = case
|
33
|
+
when _protobuf_date_column?(key) then
|
34
|
+
value.to_time.to_i
|
35
|
+
when _protobuf_datetime_column?(key) then
|
36
|
+
value.to_i
|
37
|
+
when _protobuf_time_column?(key) then
|
38
|
+
value.to_i
|
39
|
+
when _protobuf_timestamp_column?(key) then
|
40
|
+
value.to_i
|
41
|
+
else
|
42
|
+
value
|
43
|
+
end
|
44
|
+
|
45
|
+
return value
|
46
|
+
end
|
47
|
+
|
48
|
+
# Define a field transformation from a record. Accepts a Symbol,
|
49
|
+
# callable, or block that is called with the record being serialized.
|
50
|
+
#
|
51
|
+
# When given a callable or block, it is directly used to convert the field.
|
52
|
+
#
|
53
|
+
# When a symbol is given, it extracts the method with the same name.
|
54
|
+
#
|
55
|
+
# The callable or method must accept a single parameter, which is the
|
56
|
+
# proto message.
|
57
|
+
#
|
58
|
+
# Examples:
|
59
|
+
# field_from_record :public_key, :convert_public_key_to_proto
|
60
|
+
# field_from_record :status, lambda { |record| # Do some stuff... }
|
61
|
+
# field_from_record :status do |record|
|
62
|
+
# # Do some blocky stuff...
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
def field_from_record(field, transformer = nil, &block)
|
66
|
+
transformer ||= block
|
67
|
+
|
68
|
+
if transformer.is_a?(Symbol)
|
69
|
+
callable = lambda { |value| self.__send__(transformer, value) }
|
70
|
+
else
|
71
|
+
callable = transformer
|
72
|
+
end
|
73
|
+
|
74
|
+
unless callable.respond_to?(:call)
|
75
|
+
raise FieldTransformerError, 'Attribute transformers need a callable or block!'
|
76
|
+
end
|
77
|
+
|
78
|
+
_protobuf_field_transformers[field.to_sym] = callable
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define the protobuf message class that should be used to serialize the
|
82
|
+
# object to protobuf. Accepts a string or symbol and an options hash.
|
83
|
+
#
|
84
|
+
# When protobuf_message is declared, Protoable automatically extracts the
|
85
|
+
# fields from the message and automatically adds a to_proto method that
|
86
|
+
# serializes the object to protobuf.
|
87
|
+
#
|
88
|
+
# The fields that will be automatically serialized can be configured by
|
89
|
+
# passing :only or :except in the options hash. If :only is specified, only
|
90
|
+
# the specified fields will be serialized. If :except is specified, all
|
91
|
+
# field except the specified fields will be serialized.
|
92
|
+
#
|
93
|
+
# By default, deprecated fields will be serialized. To exclude deprecated
|
94
|
+
# fields, pass :deprecated => false in the options hash.
|
95
|
+
#
|
96
|
+
# Examples:
|
97
|
+
# protobuf_message :user_message
|
98
|
+
# protobuf_message "UserMessage"
|
99
|
+
# protobuf_message "Namespaced::UserMessage"
|
100
|
+
# protobuf_message :user_message, :only => [ :guid, :name ]
|
101
|
+
# protobuf_message :user_message, :except => :email_domain
|
102
|
+
# protobuf_message :user_message, :except => :email_domain, :deprecated => false
|
103
|
+
#
|
104
|
+
def protobuf_message(message = nil, options = {})
|
105
|
+
unless message.nil?
|
106
|
+
@protobuf_message = message.to_s.classify.constantize
|
107
|
+
|
108
|
+
self._protobuf_field_options = options
|
109
|
+
|
110
|
+
define_method(:to_proto) do |options = {}|
|
111
|
+
self.class.protobuf_message.new(self.fields_from_record(options))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
@protobuf_message
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# :nodoc:
|
120
|
+
def _filter_field_attributes(options = {})
|
121
|
+
options = _normalize_options(options)
|
122
|
+
|
123
|
+
fields = _filtered_fields(options)
|
124
|
+
fields &= [ options[:only] ].flatten if options[:only].present?
|
125
|
+
fields -= [ options[:except] ].flatten if options[:except].present?
|
126
|
+
|
127
|
+
fields
|
128
|
+
end
|
129
|
+
|
130
|
+
# :nodoc:
|
131
|
+
def _filtered_fields(options = {})
|
132
|
+
exclude_deprecated = ! options.fetch(:deprecated, true)
|
133
|
+
|
134
|
+
fields = self.class.protobuf_message.all_fields.map do |field|
|
135
|
+
next if field.nil?
|
136
|
+
next if exclude_deprecated && field.deprecated?
|
137
|
+
field.name.to_sym
|
138
|
+
end
|
139
|
+
fields.compact!
|
140
|
+
|
141
|
+
fields
|
142
|
+
end
|
143
|
+
|
144
|
+
# :nodoc:
|
145
|
+
def _normalize_options(options)
|
146
|
+
options ||= {}
|
147
|
+
options[:only] ||= [] if options.fetch(:except, false)
|
148
|
+
options[:except] ||= [] if options.fetch(:only, false)
|
149
|
+
|
150
|
+
self.class._protobuf_field_options.merge(options)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Extracts attributes that correspond to fields on the specified protobuf
|
154
|
+
# message, performing any necessary column conversions on them. Accepts a
|
155
|
+
# hash of options for specifying which fields should be serialized.
|
156
|
+
#
|
157
|
+
# Examples:
|
158
|
+
# fields_from_record(:only => [ :guid, :name ])
|
159
|
+
# fields_from_record(:except => :email_domain)
|
160
|
+
# fields_from_record(:include => :email_domain)
|
161
|
+
# fields_from_record(:except => :email_domain, :deprecated => false)
|
162
|
+
#
|
163
|
+
def fields_from_record(options = {})
|
164
|
+
field_attributes = _filter_field_attributes(options)
|
165
|
+
field_attributes += [ options.fetch(:include, []) ]
|
166
|
+
field_attributes.flatten!
|
167
|
+
field_attributes.compact!
|
168
|
+
field_attributes.uniq!
|
169
|
+
|
170
|
+
field_attributes = field_attributes.inject({}) do |hash, field|
|
171
|
+
if _protobuf_field_transformers.has_key?(field)
|
172
|
+
hash[field] = _protobuf_field_transformers[field].call(self)
|
173
|
+
else
|
174
|
+
value = respond_to?(field) ? __send__(field) : nil
|
175
|
+
hash[field] = _protobuf_convert_attributes_to_fields(field, value)
|
176
|
+
end
|
177
|
+
hash
|
178
|
+
end
|
179
|
+
|
180
|
+
field_attributes
|
181
|
+
end
|
182
|
+
|
183
|
+
# :nodoc:
|
184
|
+
def _protobuf_convert_attributes_to_fields(field, value)
|
185
|
+
self.class._protobuf_convert_attributes_to_fields(field, value)
|
186
|
+
end
|
187
|
+
|
188
|
+
# :nodoc:
|
189
|
+
def _protobuf_field_transformers
|
190
|
+
self.class._protobuf_field_transformers
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'heredity/inheritable_class_instance_variables'
|
3
|
+
|
4
|
+
module Protobuf
|
5
|
+
module ActiveRecord
|
6
|
+
module Transformation
|
7
|
+
extend ::ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include ::Heredity::InheritableClassInstanceVariables
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :_protobuf_attribute_transformers
|
14
|
+
end
|
15
|
+
|
16
|
+
@_protobuf_attribute_transformers = {}
|
17
|
+
|
18
|
+
inheritable_attributes :_protobuf_attribute_transformers
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Filters accessible attributes that exist in the given protobuf message's
|
23
|
+
# fields or have attribute transformers defined for them.
|
24
|
+
#
|
25
|
+
# Returns a hash of attribute fields with their respective values.
|
26
|
+
#
|
27
|
+
# :nodoc:
|
28
|
+
def _filter_attribute_fields(proto)
|
29
|
+
fields = proto.to_hash
|
30
|
+
fields.select! do |key, value|
|
31
|
+
field = proto.get_field_by_name(key) || proto.get_ext_field_by_name(key)
|
32
|
+
proto.has_field?(key) && !field.repeated?
|
33
|
+
end
|
34
|
+
|
35
|
+
attribute_fields = _filtered_attributes.inject({}) do |hash, column_name|
|
36
|
+
symbolized_column = column_name.to_sym
|
37
|
+
|
38
|
+
if fields.has_key?(symbolized_column) ||
|
39
|
+
_protobuf_attribute_transformers.has_key?(symbolized_column)
|
40
|
+
hash[symbolized_column] = fields[symbolized_column]
|
41
|
+
end
|
42
|
+
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
|
46
|
+
attribute_fields
|
47
|
+
end
|
48
|
+
|
49
|
+
# Overidden by mass assignment security when protected attributes is loaded.
|
50
|
+
#
|
51
|
+
# :nodoc:
|
52
|
+
def _filtered_attributes
|
53
|
+
return self.attribute_names
|
54
|
+
end
|
55
|
+
|
56
|
+
# :nodoc:
|
57
|
+
def _protobuf_convert_fields_to_attributes(key, value)
|
58
|
+
return value if value.nil?
|
59
|
+
|
60
|
+
value = case
|
61
|
+
when _protobuf_date_column?(key) then
|
62
|
+
convert_int64_to_date(value)
|
63
|
+
when _protobuf_datetime_column?(key) then
|
64
|
+
convert_int64_to_datetime(value)
|
65
|
+
when _protobuf_time_column?(key) then
|
66
|
+
convert_int64_to_time(value)
|
67
|
+
when _protobuf_timestamp_column?(key) then
|
68
|
+
convert_int64_to_time(value)
|
69
|
+
else
|
70
|
+
value
|
71
|
+
end
|
72
|
+
|
73
|
+
return value
|
74
|
+
end
|
75
|
+
|
76
|
+
# Define an attribute transformation from protobuf. Accepts a Symbol,
|
77
|
+
# callable, or block.
|
78
|
+
#
|
79
|
+
# When given a callable or block, it is directly used to convert the field.
|
80
|
+
#
|
81
|
+
# When a symbol is given, it extracts the method with the same name.
|
82
|
+
#
|
83
|
+
# The callable or method must accept a single parameter, which is the
|
84
|
+
# proto message.
|
85
|
+
#
|
86
|
+
# Examples:
|
87
|
+
# attribute_from_proto :public_key, :extract_public_key_from_proto
|
88
|
+
# attribute_from_proto :status, lambda { |proto| # Do some stuff... }
|
89
|
+
# attribute_from_proto :status do |proto|
|
90
|
+
# # Do some blocky stuff...
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
def attribute_from_proto(attribute, transformer = nil, &block)
|
94
|
+
transformer ||= block
|
95
|
+
|
96
|
+
if transformer.is_a?(Symbol)
|
97
|
+
callable = lambda { |value| self.__send__(transformer, value) }
|
98
|
+
else
|
99
|
+
callable = transformer
|
100
|
+
end
|
101
|
+
|
102
|
+
unless callable.respond_to?(:call)
|
103
|
+
raise AttributeTransformerError, 'Attribute transformers need a callable or block!'
|
104
|
+
end
|
105
|
+
|
106
|
+
_protobuf_attribute_transformers[attribute.to_sym] = callable
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Creates a hash of attributes from a given protobuf message.
|
111
|
+
#
|
112
|
+
# It converts and transforms field values using the field converters and
|
113
|
+
# attribute transformers, ignoring repeated and nil fields.
|
114
|
+
#
|
115
|
+
def attributes_from_proto(proto)
|
116
|
+
attribute_fields = _filter_attribute_fields(proto)
|
117
|
+
|
118
|
+
attributes = attribute_fields.inject({}) do |hash, (key, value)|
|
119
|
+
if _protobuf_attribute_transformers.has_key?(key)
|
120
|
+
attribute = _protobuf_attribute_transformers[key].call(proto)
|
121
|
+
hash[key] = attribute unless attribute.nil?
|
122
|
+
else
|
123
|
+
hash[key] = _protobuf_convert_fields_to_attributes(key, value)
|
124
|
+
end
|
125
|
+
|
126
|
+
hash
|
127
|
+
end
|
128
|
+
|
129
|
+
attributes
|
130
|
+
end
|
131
|
+
|
132
|
+
# :nodoc:
|
133
|
+
def convert_int64_to_time(int64)
|
134
|
+
Time.at(int64.to_i)
|
135
|
+
end
|
136
|
+
|
137
|
+
# :nodoc:
|
138
|
+
def convert_int64_to_date(int64)
|
139
|
+
convert_int64_to_time(int64).utc.to_date
|
140
|
+
end
|
141
|
+
|
142
|
+
# :nodoc:
|
143
|
+
def convert_int64_to_datetime(int64)
|
144
|
+
convert_int64_to_time(int64).to_datetime
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Calls up to the class version of the method.
|
149
|
+
#
|
150
|
+
def attributes_from_proto(proto)
|
151
|
+
self.class.attributes_from_proto(proto)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Protobuf
|
4
|
+
module ActiveRecord
|
5
|
+
module Validations
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Validates whether the value of the specified attribute is available in
|
10
|
+
# the given Protobuf Enum. The enumeration should be passed as a class
|
11
|
+
# that defines the enumeration:
|
12
|
+
#
|
13
|
+
# ```
|
14
|
+
# class User < ActiveRecord::Base
|
15
|
+
# include ::Protoable
|
16
|
+
#
|
17
|
+
# validates_enumeration_of :role_type, :with => RoleType, :allow_nil => true
|
18
|
+
# end
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# In this example, RoleType is a defined as a protobuf enum.
|
22
|
+
#
|
23
|
+
# It accepts the same options as `validates_inclusion_of` (the :in option
|
24
|
+
# is automatically set and will be overwritten).
|
25
|
+
#
|
26
|
+
def validates_enumeration_of(*args)
|
27
|
+
options = args.extract_options!
|
28
|
+
enumerable = options.delete(:with)
|
29
|
+
|
30
|
+
raise ArgumentError, ":with must be specified" if enumerable.nil?
|
31
|
+
|
32
|
+
if enumerable < ::Protobuf::Enum
|
33
|
+
options[:in] = enumerable.values.values.map(&:value)
|
34
|
+
end
|
35
|
+
|
36
|
+
args << options
|
37
|
+
|
38
|
+
validates_inclusion_of(*args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'protobuf/
|
4
|
+
require 'protobuf/active_record/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = "protobuf-activerecord"
|
@@ -20,16 +20,15 @@ Gem::Specification.new do |gem|
|
|
20
20
|
##
|
21
21
|
# Dependencies
|
22
22
|
#
|
23
|
-
gem.add_dependency "activerecord", "
|
24
|
-
gem.add_dependency "activesupport", "
|
23
|
+
gem.add_dependency "activerecord", ">= 3.2.0"
|
24
|
+
gem.add_dependency "activesupport", ">= 3.2.0"
|
25
25
|
gem.add_dependency "heredity"
|
26
|
-
gem.add_dependency "protobuf", ">= 2.
|
26
|
+
gem.add_dependency "protobuf", ">= 2.2.0"
|
27
27
|
|
28
28
|
##
|
29
29
|
# Development dependencies
|
30
30
|
#
|
31
31
|
gem.add_development_dependency "rake"
|
32
|
-
gem.add_development_dependency "geminabox"
|
33
32
|
gem.add_development_dependency "rspec"
|
34
33
|
gem.add_development_dependency "rspec-pride"
|
35
34
|
gem.add_development_dependency "pry-nav"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe Protobuf::ActiveRecord::Columns do
|
4
4
|
describe "._protobuf_map_columns" do
|
5
5
|
context "when the class has a table" do
|
6
6
|
let(:expected_column_names) {
|
@@ -27,7 +27,7 @@ describe Protoable::Columns do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
context "column type predicates" do
|
32
32
|
before { User.stub(:_protobuf_column_types).and_return(Hash.new) }
|
33
33
|
after { User.unstub(:_protobuf_column_types) }
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe Protobuf::ActiveRecord::Persistence do
|
4
4
|
let(:user) { User.new(user_attributes) }
|
5
5
|
let(:user_attributes) { { :first_name => 'foo', :last_name => 'bar', :email => 'foo@test.co' } }
|
6
6
|
let(:proto_hash) { { :name => 'foo bar', :email => 'foo@test.co' } }
|