protobuf-activerecord 1.0.5 → 1.1.0
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/Rakefile +13 -1
- data/lib/protoable.rb +2 -0
- data/lib/protoable/convert.rb +3 -3
- data/lib/protoable/errors.rb +13 -9
- data/lib/protoable/fields.rb +34 -35
- data/lib/protoable/persistence.rb +5 -5
- data/lib/protoable/scope.rb +64 -0
- data/lib/protoable/serialization.rb +24 -28
- data/lib/protobuf-activerecord.rb +2 -0
- data/lib/protobuf/activerecord/version.rb +1 -1
- data/lib/protobuf_ext/message.rb +12 -0
- data/spec/protoable/convert_spec.rb +12 -12
- data/spec/protoable/fields_spec.rb +7 -7
- data/spec/protoable/persistence_spec.rb +5 -5
- data/spec/protoable/scope_spec.rb +41 -0
- data/spec/protoable/serialization_spec.rb +16 -15
- data/spec/support/db/setup.rb +1 -0
- data/spec/support/definitions/user.proto +10 -0
- data/spec/support/models/user.rb +6 -2
- data/spec/support/protobuf/user.pb.rb +23 -6
- metadata +10 -4
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
|
4
4
|
desc "Run specs"
|
@@ -6,3 +6,15 @@ RSpec::Core::RakeTask.new(:spec)
|
|
6
6
|
|
7
7
|
desc "Run specs (default)"
|
8
8
|
task :default => :spec
|
9
|
+
|
10
|
+
desc "Remove protobuf definitions that have been compiled"
|
11
|
+
task :clean do
|
12
|
+
FileUtils.rm(Dir.glob("spec/support/protobuf/**/*.proto"))
|
13
|
+
puts "Cleaned"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Compile spec/support protobuf definitions"
|
17
|
+
task :compile, [] => :clean do
|
18
|
+
cmd = "rprotoc --ruby_out=spec/support/protobuf --proto_path=spec/support/definitions spec/support/definitions/*.proto"
|
19
|
+
sh(cmd)
|
20
|
+
end
|
data/lib/protoable.rb
CHANGED
@@ -2,11 +2,13 @@ require 'protoable/convert'
|
|
2
2
|
require 'protoable/errors'
|
3
3
|
require 'protoable/fields'
|
4
4
|
require 'protoable/persistence'
|
5
|
+
require 'protoable/scope'
|
5
6
|
require 'protoable/serialization'
|
6
7
|
|
7
8
|
module Protoable
|
8
9
|
def self.included(klass)
|
9
10
|
klass.extend Protoable::Fields
|
11
|
+
klass.extend Protoable::Scope
|
10
12
|
|
11
13
|
klass.__send__(:include, Protoable::Convert)
|
12
14
|
klass.__send__(:include, Protoable::Persistence)
|
data/lib/protoable/convert.rb
CHANGED
@@ -17,10 +17,10 @@ module Protoable
|
|
17
17
|
convert_int64_to_time(int64).to_datetime
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
20
|
+
def _protobuf_convert_attributes_to_fields(key, value)
|
21
21
|
value = case
|
22
|
-
when
|
23
|
-
|
22
|
+
when _protobuf_attribute_converters.has_key?(key.to_sym) then
|
23
|
+
_protobuf_attribute_converters[key.to_sym].call(value)
|
24
24
|
when _protobuf_date_column?(key) then
|
25
25
|
value.to_time.to_i
|
26
26
|
when _protobuf_datetime_column?(key) then
|
data/lib/protoable/errors.rb
CHANGED
@@ -6,18 +6,22 @@ module Protoable
|
|
6
6
|
class ProtoableError < StandardError
|
7
7
|
end
|
8
8
|
|
9
|
-
# Raised by Protoable.
|
10
|
-
#
|
11
|
-
class
|
9
|
+
# Raised by Protoable.protoable_attribute when the convert method given is
|
10
|
+
# not callable.
|
11
|
+
class AttributeConverterError < ProtoableError
|
12
12
|
end
|
13
13
|
|
14
|
-
# Raised by Protoable.
|
15
|
-
# given is not
|
16
|
-
class
|
14
|
+
# Raised by Protoable.attribute_from_proto when the transformer method
|
15
|
+
# given is not callable.
|
16
|
+
class AttributeTransformerError < ProtoableError
|
17
17
|
end
|
18
|
-
|
19
|
-
# Raised by Protoable.
|
20
|
-
# given
|
18
|
+
|
19
|
+
# Raised by Protoable.convert_field when the convert method
|
20
|
+
# given not callable.
|
21
21
|
class FieldConverterError < ProtoableError
|
22
22
|
end
|
23
|
+
|
24
|
+
# Raised by Protoable.field_scope when given scope is not defined.
|
25
|
+
class SearchScopeError < ProtoableError
|
26
|
+
end
|
23
27
|
end
|
data/lib/protoable/fields.rb
CHANGED
@@ -9,20 +9,17 @@ module Protoable
|
|
9
9
|
klass.class_eval do
|
10
10
|
class << self
|
11
11
|
attr_accessor :_protobuf_columns, :_protobuf_column_types,
|
12
|
-
:
|
13
|
-
|
14
|
-
alias_method :convert_field_to_column, :convert_field
|
15
|
-
alias_method :transform_column_from_proto, :transform_column
|
12
|
+
:_protobuf_attribute_transformers, :_protobuf_field_converters
|
16
13
|
end
|
17
14
|
|
15
|
+
@_protobuf_attribute_transformers = {}
|
18
16
|
@_protobuf_columns = {}
|
19
17
|
@_protobuf_column_types = Hash.new { |h,k| h[k] = [] }
|
20
|
-
@_protobuf_column_transformers = {}
|
21
18
|
@_protobuf_field_converters = {}
|
22
19
|
|
23
20
|
# NOTE: Make sure each inherited object has the database layout
|
24
|
-
inheritable_attributes :
|
25
|
-
:
|
21
|
+
inheritable_attributes :_protobuf_attribute_transformers, :_protobuf_columns,
|
22
|
+
:_protobuf_column_types, :_protobuf_field_converters
|
26
23
|
end
|
27
24
|
|
28
25
|
_protobuf_map_columns(klass)
|
@@ -39,39 +36,33 @@ module Protoable
|
|
39
36
|
end
|
40
37
|
|
41
38
|
module ClassMethods
|
42
|
-
# Define a field conversion from protobuf to db. Accepts a
|
43
|
-
#
|
39
|
+
# Define a field conversion from protobuf to db. Accepts a Symbol,
|
40
|
+
# Hash, callable or block.
|
44
41
|
#
|
45
|
-
# When given a callable, it is directly used to convert the field.
|
42
|
+
# When given a callable or block, it is directly used to convert the field.
|
46
43
|
#
|
47
44
|
# When a Hash is given, :from and :to keys are expected and expand
|
48
45
|
# to extracting a class method in the format of
|
49
46
|
# "convert_#{from}_to_#{to}".
|
50
47
|
#
|
51
|
-
# When a symbol is given, it extracts the method with the same name
|
52
|
-
# if any. When method is not available it is assumed as the "from"
|
53
|
-
# data type, and the "to" value is extracted based on the
|
54
|
-
# name of the column.
|
48
|
+
# When a symbol is given, it extracts the method with the same name.
|
55
49
|
#
|
56
50
|
# Examples:
|
57
|
-
# convert_field :created_at, :int64
|
58
51
|
# convert_field :public_key, :extract_public_key_from_proto
|
59
|
-
# convert_field :status, lambda { |proto_field| ... }
|
60
52
|
# convert_field :symmetric_key, :from => :base64, :to => :encoded_string
|
53
|
+
# convert_field :status, lambda { |proto_field| # Do some stuff... }
|
54
|
+
# convert_field :status do |proto_field|
|
55
|
+
# # Do some blocky stuff...
|
56
|
+
# end
|
61
57
|
#
|
62
|
-
def convert_field(field,
|
63
|
-
|
64
|
-
|
58
|
+
def convert_field(field, converter = nil, &blk)
|
59
|
+
converter ||= blk
|
60
|
+
converter = :"convert_#{converter[:from]}_to_#{converter[:to]}" if converter.is_a?(Hash)
|
65
61
|
|
66
|
-
if
|
67
|
-
|
68
|
-
column = _protobuf_columns[field.to_sym]
|
69
|
-
transformer = :"convert_#{transformer}_to_#{column.try(:type)}"
|
70
|
-
end
|
71
|
-
|
72
|
-
callable = lambda { |value| self.__send__(transformer, value) }
|
62
|
+
if converter.is_a?(Symbol)
|
63
|
+
callable = lambda { |value| self.__send__(converter, value) }
|
73
64
|
else
|
74
|
-
callable =
|
65
|
+
callable = converter
|
75
66
|
end
|
76
67
|
|
77
68
|
unless callable.respond_to?(:call)
|
@@ -81,10 +72,10 @@ module Protoable
|
|
81
72
|
_protobuf_field_converters[field.to_sym] = callable
|
82
73
|
end
|
83
74
|
|
84
|
-
# Define
|
85
|
-
# or
|
75
|
+
# Define an attribute transformation from protobuf. Accepts a Symbol,
|
76
|
+
# callable, or block.
|
86
77
|
#
|
87
|
-
# When given a callable, it is directly used to convert the field.
|
78
|
+
# When given a callable or block, it is directly used to convert the field.
|
88
79
|
#
|
89
80
|
# When a symbol is given, it extracts the method with the same name.
|
90
81
|
#
|
@@ -92,10 +83,13 @@ module Protoable
|
|
92
83
|
# proto message.
|
93
84
|
#
|
94
85
|
# Examples:
|
95
|
-
#
|
96
|
-
#
|
86
|
+
# attribute_from_proto :public_key, :extract_public_key_from_proto
|
87
|
+
# attribute_from_proto :status, lambda { |proto_field| # Do some stuff... }
|
88
|
+
# attribute_from_proto :status do |proto_field|
|
89
|
+
# # Do some blocky stuff...
|
90
|
+
# end
|
97
91
|
#
|
98
|
-
def
|
92
|
+
def attribute_from_proto(field, transformer = nil, &blk)
|
99
93
|
transformer ||= blk
|
100
94
|
|
101
95
|
if transformer.is_a?(Symbol)
|
@@ -105,10 +99,15 @@ module Protoable
|
|
105
99
|
end
|
106
100
|
|
107
101
|
unless callable.respond_to?(:call)
|
108
|
-
raise
|
102
|
+
raise AttributeTransformerError, 'Attribute transformers need a callable or block!'
|
109
103
|
end
|
110
104
|
|
111
|
-
|
105
|
+
_protobuf_attribute_transformers[field.to_sym] = callable
|
106
|
+
end
|
107
|
+
|
108
|
+
def transform_column(field, transformer = nil, &blk)
|
109
|
+
warn "[DEPRECATION] `transform_column` is deprecated and will be removed in v1.2. Please use `attribute_from_proto` instead."
|
110
|
+
attribute_from_proto(field, transformer, &blk)
|
112
111
|
end
|
113
112
|
end
|
114
113
|
end
|
@@ -6,7 +6,7 @@ module Protoable
|
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
# Filters accessible attributes that exist in the given protobuf message's
|
9
|
-
# fields or have
|
9
|
+
# fields or have attribute transformers defined for them.
|
10
10
|
#
|
11
11
|
# Returns a hash of attribute fields with their respective values.
|
12
12
|
#
|
@@ -20,7 +20,7 @@ module Protoable
|
|
20
20
|
symbolized_column = column_name.to_sym
|
21
21
|
|
22
22
|
if fields.has_key?(symbolized_column) ||
|
23
|
-
|
23
|
+
_protobuf_attribute_transformers.has_key?(symbolized_column)
|
24
24
|
hash[symbolized_column] = fields[symbolized_column]
|
25
25
|
end
|
26
26
|
|
@@ -33,14 +33,14 @@ module Protoable
|
|
33
33
|
# Creates a hash of attributes from a given protobuf message.
|
34
34
|
#
|
35
35
|
# It converts and transforms field values using the field converters and
|
36
|
-
#
|
36
|
+
# attribute transformers, ignoring repeated and nil fields.
|
37
37
|
#
|
38
38
|
def attributes_from_proto(proto)
|
39
39
|
attribute_fields = _filter_attribute_fields(proto)
|
40
40
|
|
41
41
|
attributes = attribute_fields.inject({}) do |hash, (key, value)|
|
42
|
-
if
|
43
|
-
hash[key] =
|
42
|
+
if _protobuf_attribute_transformers.has_key?(key)
|
43
|
+
hash[key] = _protobuf_attribute_transformers[key].call(proto)
|
44
44
|
else
|
45
45
|
hash[key] = _protobuf_convert_fields_to_columns(key, value)
|
46
46
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Protoable
|
2
|
+
module Scope
|
3
|
+
def self.extended(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
class << self
|
6
|
+
alias_method :by_fields, :search_scope
|
7
|
+
alias_method :from_proto, :search_scope
|
8
|
+
alias_method :scope_from_proto, :search_scope
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Define fields that should be searchable via `search_scope`. Accepts a
|
14
|
+
# protobuf field and an already defined scope.
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# class User < ActiveRecord::Base
|
19
|
+
# scope :by_guid, lambda { |*guids| where(:guid => guids) }
|
20
|
+
#
|
21
|
+
# field_scope :guid, :by_guid
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
def field_scope(field, scope_name)
|
25
|
+
unless self.respond_to?(scope_name)
|
26
|
+
raise Protoable::SearchScopeError, "Undefined scope :#{scope_name}. :#{scope_name} must be defined before it can be used as a field scope."
|
27
|
+
end
|
28
|
+
|
29
|
+
searchable_fields[field] = scope_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Builds and returns a Arel relation based on the fields that are present
|
33
|
+
# in the given protobuf message using the searchable fields to determine
|
34
|
+
# what scopes to use. Provides several aliases for variety.
|
35
|
+
#
|
36
|
+
# Examples:
|
37
|
+
#
|
38
|
+
# # Search starting with the default scope and searchable fields
|
39
|
+
# User.search_scope(request)
|
40
|
+
# User.by_fields(request)
|
41
|
+
# User.from_proto(request)
|
42
|
+
# User.scope_from_proto(request)
|
43
|
+
#
|
44
|
+
def search_scope(proto)
|
45
|
+
relation = scoped # Get an ARel relation to build off of
|
46
|
+
|
47
|
+
searchable_fields.each do |field, scope_name|
|
48
|
+
next unless proto.respond_to_and_has_and_present?(field)
|
49
|
+
|
50
|
+
values = [ proto.__send__(field) ].flatten
|
51
|
+
values.map!(&:to_i) if proto.get_field_by_name(field).enum?
|
52
|
+
|
53
|
+
relation = relation.__send__(scope_name, *values)
|
54
|
+
end
|
55
|
+
|
56
|
+
return relation
|
57
|
+
end
|
58
|
+
|
59
|
+
# :noapi:
|
60
|
+
def searchable_fields
|
61
|
+
@_searchable_fields ||= {}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -8,60 +8,56 @@ module Protoable
|
|
8
8
|
|
9
9
|
klass.class_eval do
|
10
10
|
class << self
|
11
|
-
attr_accessor :
|
12
|
-
|
13
|
-
alias_method :convert_column_to_field, :convert_column
|
11
|
+
attr_accessor :_protobuf_attribute_converters, :protobuf_fields
|
14
12
|
end
|
15
13
|
|
16
|
-
@
|
14
|
+
@_protobuf_attribute_converters = {}
|
17
15
|
@protobuf_fields = []
|
18
16
|
|
19
|
-
inheritable_attributes :
|
17
|
+
inheritable_attributes :_protobuf_attribute_converters, :protobuf_fields, :protobuf_message
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
23
21
|
module ClassMethods
|
24
|
-
|
25
|
-
|
22
|
+
def convert_column(field, converter = nil, &blk)
|
23
|
+
warn "[DEPRECATION] `convert_column` is deprecated and will be removed in v1.2. Please use `protoable_attribute` instead."
|
24
|
+
protoable_attribute(field, converter, &blk)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Define a custom attribute conversion for serialization to protobuf.
|
28
|
+
# Accepts a Symbol, Hash, callable or block.
|
26
29
|
#
|
27
|
-
# When given a callable, it is directly used to convert the field.
|
30
|
+
# When given a callable or block, it is directly used to convert the field.
|
28
31
|
#
|
29
32
|
# When a Hash is given, :from and :to keys are expected and expand
|
30
33
|
# to extracting a class method in the format of
|
31
34
|
# "convert_#{from}_to_#{to}".
|
32
35
|
#
|
33
|
-
# When a symbol is given, it extracts the method with the same name
|
34
|
-
# if any. When method is not available it is assumed as the "from"
|
35
|
-
# data type, and the "to" value is extracted based on the
|
36
|
-
# name of the column.
|
36
|
+
# When a symbol is given, it extracts the method with the same name.
|
37
37
|
#
|
38
38
|
# Examples:
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
39
|
+
# protoable_attribute :public_key, :extract_public_key_from_proto
|
40
|
+
# protoable_attribute :symmetric_key, :from => :base64, :to => :raw_string
|
41
|
+
# protoable_attribute :status, lambda { |proto_field| # Do stuff... }
|
42
|
+
# protoable_attribute :status do |proto_field|
|
43
|
+
# # Do some blocky stuff...
|
44
|
+
# end
|
44
45
|
#
|
45
|
-
def
|
46
|
+
def protoable_attribute(field, converter = nil, &blk)
|
46
47
|
converter ||= blk
|
47
48
|
converter = :"convert_#{converter[:from]}_to_#{converter[:to]}" if converter.is_a?(Hash)
|
48
49
|
|
49
50
|
if converter.is_a?(Symbol)
|
50
|
-
unless self.respond_to?(converter, true)
|
51
|
-
column = _protobuf_columns[field.to_sym]
|
52
|
-
converter = :"convert_#{converter}_to_#{column.try(:type)}"
|
53
|
-
end
|
54
|
-
|
55
51
|
callable = lambda { |value| __send__(converter, value) }
|
56
52
|
else
|
57
53
|
callable = converter
|
58
54
|
end
|
59
55
|
|
60
56
|
unless callable.respond_to?(:call)
|
61
|
-
raise
|
57
|
+
raise AttributeConverterError, 'Attribute converters must be a callable or block!'
|
62
58
|
end
|
63
59
|
|
64
|
-
|
60
|
+
_protobuf_attribute_converters[field.to_sym] = callable
|
65
61
|
end
|
66
62
|
|
67
63
|
# Define the protobuf message class that should be used to serialize the
|
@@ -103,7 +99,7 @@ module Protoable
|
|
103
99
|
def protoable_attributes
|
104
100
|
protoable_attributes = protobuf_fields.inject({}) do |hash, field|
|
105
101
|
value = respond_to?(field) ? __send__(field) : nil
|
106
|
-
hash[field] =
|
102
|
+
hash[field] = _protobuf_convert_attributes_to_fields(field, value)
|
107
103
|
hash
|
108
104
|
end
|
109
105
|
|
@@ -112,8 +108,8 @@ module Protoable
|
|
112
108
|
|
113
109
|
private
|
114
110
|
|
115
|
-
def
|
116
|
-
self.class.
|
111
|
+
def _protobuf_convert_attributes_to_fields(field, value)
|
112
|
+
self.class._protobuf_convert_attributes_to_fields(field, value)
|
117
113
|
end
|
118
114
|
|
119
115
|
def protobuf_fields
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'protobuf'
|
2
|
+
|
3
|
+
class Protobuf::Message
|
4
|
+
def respond_to_and_has?(key)
|
5
|
+
self.respond_to?(key) && self.has_field?(key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def respond_to_and_has_and_present?(key)
|
9
|
+
self.respond_to_and_has?(key) &&
|
10
|
+
(self.__send__(key).present? || [true, false].include?(self.__send__(key)))
|
11
|
+
end
|
12
|
+
end
|
@@ -34,24 +34,24 @@ describe Protoable::Convert do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
describe ".
|
38
|
-
context "when there is a
|
37
|
+
describe "._protobuf_convert_attributes_to_fields" do
|
38
|
+
context "when there is a attribute converter for the field" do
|
39
39
|
let(:email_value) { "foo@test.co" }
|
40
40
|
let(:email_converter) { User.method(:convert_email_to_lowercase) }
|
41
41
|
|
42
|
-
before { User.stub(:
|
42
|
+
before { User.stub(:_protobuf_attribute_converters).and_return({ :email => email_converter }) }
|
43
43
|
|
44
|
-
it "calls the
|
44
|
+
it "calls the attribute converter with the given value" do
|
45
45
|
email_converter.should_receive(:call).with(email_value)
|
46
|
-
User.
|
46
|
+
User._protobuf_convert_attributes_to_fields(:email, email_value)
|
47
47
|
end
|
48
48
|
|
49
49
|
context "and it's corresponding column type has a default converter" do
|
50
50
|
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
51
51
|
|
52
|
-
it "calls the
|
52
|
+
it "calls the attribute converter with the given value" do
|
53
53
|
email_converter.should_receive(:call).with(email_value)
|
54
|
-
User.
|
54
|
+
User._protobuf_convert_attributes_to_fields(:email, email_value)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -63,7 +63,7 @@ describe Protoable::Convert do
|
|
63
63
|
before { User.stub(:_protobuf_date_column?).and_return(true) }
|
64
64
|
|
65
65
|
it "converts the given value to an integer" do
|
66
|
-
User.
|
66
|
+
User._protobuf_convert_attributes_to_fields(:foo_date, date).should eq integer
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -74,7 +74,7 @@ describe Protoable::Convert do
|
|
74
74
|
before { User.stub(:_protobuf_datetime_column?).and_return(true) }
|
75
75
|
|
76
76
|
it "converts the given value to an integer" do
|
77
|
-
User.
|
77
|
+
User._protobuf_convert_attributes_to_fields(:foo_datetime, datetime).should eq integer
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -85,7 +85,7 @@ describe Protoable::Convert do
|
|
85
85
|
before { User.stub(:_protobuf_time_column?).and_return(true) }
|
86
86
|
|
87
87
|
it "converts the given value to an integer" do
|
88
|
-
User.
|
88
|
+
User._protobuf_convert_attributes_to_fields(:foo_time, time).should eq integer
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
@@ -96,7 +96,7 @@ describe Protoable::Convert do
|
|
96
96
|
before { User.stub(:_protobuf_timestamp_column?).and_return(true) }
|
97
97
|
|
98
98
|
it "converts the given value to an integer" do
|
99
|
-
User.
|
99
|
+
User._protobuf_convert_attributes_to_fields(:foo_timestamp, timestamp).should eq integer
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
@@ -104,7 +104,7 @@ describe Protoable::Convert do
|
|
104
104
|
let(:value) { "Foo" }
|
105
105
|
|
106
106
|
it "returns the given value" do
|
107
|
-
User.
|
107
|
+
User._protobuf_convert_attributes_to_fields(:foo, value).should eq value
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
@@ -68,21 +68,21 @@ describe Protoable::Fields do
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
describe ".
|
71
|
+
describe ".attribute_from_proto" do
|
72
72
|
context "when the given converter is a symbol" do
|
73
73
|
let(:callable) { lambda { |value| User.__send__(:extract_first_name) } }
|
74
74
|
|
75
|
-
before { User.
|
75
|
+
before { User.attribute_from_proto :first_name, :extract_first_name }
|
76
76
|
|
77
77
|
it "creates a callable method object from the converter" do
|
78
78
|
User.should_receive(:extract_first_name)
|
79
|
-
User.
|
79
|
+
User._protobuf_attribute_transformers[:first_name].call(1)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
83
|
context "when the given converter is not callable" do
|
84
84
|
it "raises an exception" do
|
85
|
-
expect { User.
|
85
|
+
expect { User.attribute_from_proto :name, nil }.to raise_exception(Protoable::AttributeTransformerError)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
@@ -90,12 +90,12 @@ describe Protoable::Fields do
|
|
90
90
|
let(:callable) { lambda { |proto| nil } }
|
91
91
|
|
92
92
|
before {
|
93
|
-
User.stub(:
|
94
|
-
User.
|
93
|
+
User.stub(:_protobuf_attribute_transformers).and_return(Hash.new)
|
94
|
+
User.attribute_from_proto :account_id, callable
|
95
95
|
}
|
96
96
|
|
97
97
|
it "adds the given converter to the list of protobuf field transformers" do
|
98
|
-
User.
|
98
|
+
User._protobuf_attribute_transformers[:account_id] = callable
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
@@ -4,7 +4,7 @@ describe Protoable::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' } }
|
7
|
-
let(:proto) {
|
7
|
+
let(:proto) { UserMessage.new(proto_hash) }
|
8
8
|
|
9
9
|
describe "._filter_attribute_fields" do
|
10
10
|
it "includes fields that have values" do
|
@@ -23,22 +23,22 @@ describe Protoable::Persistence do
|
|
23
23
|
attribute_fields.has_key?(:email).should be_false
|
24
24
|
end
|
25
25
|
|
26
|
-
it "includes attributes that aren't fields, but have
|
27
|
-
User.stub(:
|
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
28
|
attribute_fields = User._filter_attribute_fields(proto)
|
29
29
|
attribute_fields.has_key?(:account_id).should be_true
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
describe ".attributes_from_proto" do
|
34
|
-
context "when a
|
34
|
+
context "when a attribute transformer is defined for the field" do
|
35
35
|
it "transforms the field value" do
|
36
36
|
attribute_fields = User.attributes_from_proto(proto)
|
37
37
|
attribute_fields[:first_name].should eq user_attributes[:first_name]
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
context "when
|
41
|
+
context "when attribute transformer is not defined for the field" do
|
42
42
|
before {
|
43
43
|
User.stub(:_protobuf_convert_fields_to_columns) do |key, value|
|
44
44
|
value
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Protoable::Scope do
|
4
|
+
describe ".search_scope" do
|
5
|
+
let(:request) { UserSearchMessage.new(:guid => ["foo"], :email => ["foo@test.co"]) }
|
6
|
+
|
7
|
+
it "builds scopes for searchable fields" do
|
8
|
+
User.stub(:searchable_fields).and_return({ :email => :by_email })
|
9
|
+
User.search_scope(request).should eq User.by_email("foo@test.co")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "is chainable" do
|
13
|
+
User.limit(1).search_scope(request).order(:email).should eq User.limit(1).order(:email)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when a searchable field does not have a value" do
|
17
|
+
let(:request) { UserSearchMessage.new(:email => ["foo@test.co"]) }
|
18
|
+
|
19
|
+
it "doesn't build a scope from that field" do
|
20
|
+
User.stub(:searchable_fields).and_return({ :email => :by_email })
|
21
|
+
User.search_scope(request).should eq User.by_email("foo@test.co")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ".field_scope" do
|
27
|
+
before { @fields = User.instance_variable_get("@_searchable_fields") }
|
28
|
+
after { User.instance_variable_set("@_searchable_fields", @fields) }
|
29
|
+
|
30
|
+
context "when given a search scope that is not defined" do
|
31
|
+
it "raises an exception" do
|
32
|
+
expect { User.field_scope :name, :foo }.to raise_exception(/Undefined scope :foo/)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "stores the search scope in the searchable fields hash using the field as the key" do
|
37
|
+
User.field_scope :guid, :by_guid
|
38
|
+
User.searchable_fields[:guid].should eq :by_guid
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,44 +1,44 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Protoable::Serialization do
|
4
|
-
let(:protobuf_message) {
|
4
|
+
let(:protobuf_message) { UserMessage }
|
5
5
|
|
6
|
-
describe ".
|
6
|
+
describe ".protoable_attribute" do
|
7
7
|
context "when the given converter is a hash" do
|
8
8
|
let(:method) { lambda { |value| User.__send__(:convert_base64_to_string, value) } }
|
9
9
|
|
10
|
-
before { User.
|
10
|
+
before { User.protoable_attribute :public_key, :from => :base64, :to => :string }
|
11
11
|
|
12
12
|
it "determines the method using the hash's :to and :from keys" do
|
13
13
|
User.should_receive(:convert_base64_to_string)
|
14
|
-
User.
|
14
|
+
User._protobuf_attribute_converters[:public_key].call(1)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
context "when the given converter is a symbol" do
|
19
19
|
let(:callable) { lambda { |value| User.__send__(:convert_email_to_lowercase, value) } }
|
20
20
|
|
21
|
-
before { User.
|
21
|
+
before { User.protoable_attribute :email, :convert_email_to_lowercase }
|
22
22
|
|
23
23
|
it "creates a callable method object from the converter" do
|
24
24
|
User.should_receive(:convert_email_to_lowercase)
|
25
|
-
User.
|
25
|
+
User._protobuf_attribute_converters[:email].call(1)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
context "when the given converter is not callable" do
|
30
30
|
it "raises an exception" do
|
31
|
-
expect { User.
|
31
|
+
expect { User.protoable_attribute :email, nil }.to raise_exception(Protoable::AttributeConverterError)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
context "when the given converter is callable" do
|
36
36
|
let(:callable) { lambda { |value| value } }
|
37
37
|
|
38
|
-
before { User.
|
38
|
+
before { User.protoable_attribute :email, callable }
|
39
39
|
|
40
|
-
it "adds the given converter to list of
|
41
|
-
User.
|
40
|
+
it "adds the given converter to list of attribute converters" do
|
41
|
+
User._protobuf_attribute_converters[:email].should eq callable
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -47,7 +47,7 @@ describe Protoable::Serialization do
|
|
47
47
|
before { User.protobuf_message(protobuf_message) }
|
48
48
|
|
49
49
|
context "given a value" do
|
50
|
-
let(:protobuf_fields) { [ :
|
50
|
+
let(:protobuf_fields) { [ :guid, :name, :email ] }
|
51
51
|
|
52
52
|
it "sets .protobuf_fields" do
|
53
53
|
User.protobuf_fields.should =~ protobuf_fields
|
@@ -76,19 +76,20 @@ describe Protoable::Serialization do
|
|
76
76
|
describe "#protoable_attributes" do
|
77
77
|
let(:attributes) {
|
78
78
|
{
|
79
|
-
:
|
80
|
-
:
|
79
|
+
:guid => "foo",
|
80
|
+
:first_name => "bar",
|
81
|
+
:last_name => "baz",
|
81
82
|
:email => "foo@test.co"
|
82
83
|
}
|
83
84
|
}
|
84
|
-
let(:protoable_attributes) { { :
|
85
|
+
let(:protoable_attributes) { { :guid => user.guid, :name => user.name, :email => user.email } }
|
85
86
|
|
86
87
|
it "returns a hash of protobuf fields that this object has getters for" do
|
87
88
|
user.protoable_attributes.should eq protoable_attributes
|
88
89
|
end
|
89
90
|
|
90
91
|
it "converts attributes values for protobuf messages" do
|
91
|
-
user.should_receive(:
|
92
|
+
user.should_receive(:_protobuf_convert_attributes_to_fields).any_number_of_times
|
92
93
|
user.protoable_attributes
|
93
94
|
end
|
94
95
|
end
|
data/spec/support/db/setup.rb
CHANGED
data/spec/support/models/user.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
class User < ActiveRecord::Base
|
2
2
|
include Protoable
|
3
3
|
|
4
|
+
scope :by_guid, lambda { |*guids| where(:guid => guids) }
|
5
|
+
scope :by_email, lambda { |*emails| where(:email => emails) }
|
6
|
+
|
7
|
+
attribute_from_proto :first_name, :extract_first_name
|
8
|
+
attribute_from_proto :last_name, :extract_last_name
|
9
|
+
|
4
10
|
def self.convert_base64_to_string(value)
|
5
11
|
value
|
6
12
|
end
|
@@ -17,7 +23,6 @@ class User < ActiveRecord::Base
|
|
17
23
|
|
18
24
|
first_name
|
19
25
|
end
|
20
|
-
transform_column :first_name, :extract_first_name
|
21
26
|
|
22
27
|
def self.extract_last_name(proto)
|
23
28
|
if proto.has_field?(:name)
|
@@ -28,7 +33,6 @@ class User < ActiveRecord::Base
|
|
28
33
|
|
29
34
|
last_name
|
30
35
|
end
|
31
|
-
transform_column :last_name, :extract_last_name
|
32
36
|
|
33
37
|
def name
|
34
38
|
"#{first_name} #{last_name}"
|
@@ -1,9 +1,26 @@
|
|
1
|
+
##
|
2
|
+
# This file is auto-generated. DO NOT EDIT!
|
3
|
+
#
|
1
4
|
require 'protobuf/message'
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
|
7
|
+
##
|
8
|
+
# Message Classes
|
9
|
+
#
|
10
|
+
class UserMessage < ::Protobuf::Message; end
|
11
|
+
class UserSearchMessage < ::Protobuf::Message; end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Message Fields
|
15
|
+
#
|
16
|
+
class UserMessage
|
17
|
+
optional ::Protobuf::Field::StringField, :guid, 1
|
18
|
+
optional ::Protobuf::Field::StringField, :name, 2
|
19
|
+
optional ::Protobuf::Field::StringField, :email, 3
|
9
20
|
end
|
21
|
+
|
22
|
+
class UserSearchMessage
|
23
|
+
repeated ::Protobuf::Field::StringField, :guid, 1
|
24
|
+
repeated ::Protobuf::Field::StringField, :email, 2
|
25
|
+
end
|
26
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protobuf-activerecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -207,17 +207,21 @@ files:
|
|
207
207
|
- lib/protoable/errors.rb
|
208
208
|
- lib/protoable/fields.rb
|
209
209
|
- lib/protoable/persistence.rb
|
210
|
+
- lib/protoable/scope.rb
|
210
211
|
- lib/protoable/serialization.rb
|
211
212
|
- lib/protobuf-activerecord.rb
|
212
213
|
- lib/protobuf/activerecord/version.rb
|
214
|
+
- lib/protobuf_ext/message.rb
|
213
215
|
- protobuf-activerecord.gemspec
|
214
216
|
- spec/protoable/convert_spec.rb
|
215
217
|
- spec/protoable/fields_spec.rb
|
216
218
|
- spec/protoable/persistence_spec.rb
|
219
|
+
- spec/protoable/scope_spec.rb
|
217
220
|
- spec/protoable/serialization_spec.rb
|
218
221
|
- spec/spec_helper.rb
|
219
222
|
- spec/support/db.rb
|
220
223
|
- spec/support/db/setup.rb
|
224
|
+
- spec/support/definitions/user.proto
|
221
225
|
- spec/support/models.rb
|
222
226
|
- spec/support/models/user.rb
|
223
227
|
- spec/support/protobuf.rb
|
@@ -236,7 +240,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
236
240
|
version: '0'
|
237
241
|
segments:
|
238
242
|
- 0
|
239
|
-
hash: -
|
243
|
+
hash: -185204863767589185
|
240
244
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
241
245
|
none: false
|
242
246
|
requirements:
|
@@ -245,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
249
|
version: '0'
|
246
250
|
segments:
|
247
251
|
- 0
|
248
|
-
hash: -
|
252
|
+
hash: -185204863767589185
|
249
253
|
requirements: []
|
250
254
|
rubyforge_project:
|
251
255
|
rubygems_version: 1.8.24
|
@@ -256,10 +260,12 @@ test_files:
|
|
256
260
|
- spec/protoable/convert_spec.rb
|
257
261
|
- spec/protoable/fields_spec.rb
|
258
262
|
- spec/protoable/persistence_spec.rb
|
263
|
+
- spec/protoable/scope_spec.rb
|
259
264
|
- spec/protoable/serialization_spec.rb
|
260
265
|
- spec/spec_helper.rb
|
261
266
|
- spec/support/db.rb
|
262
267
|
- spec/support/db/setup.rb
|
268
|
+
- spec/support/definitions/user.proto
|
263
269
|
- spec/support/models.rb
|
264
270
|
- spec/support/models/user.rb
|
265
271
|
- spec/support/protobuf.rb
|