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.
Files changed (35) hide show
  1. checksums.yaml +6 -14
  2. data/Gemfile +0 -4
  3. data/lib/protobuf-activerecord.rb +26 -3
  4. data/lib/protobuf/active_record/attribute_methods.rb +35 -0
  5. data/lib/protobuf/active_record/columns.rb +74 -0
  6. data/lib/protobuf/active_record/config.rb +11 -0
  7. data/lib/protobuf/active_record/errors.rb +23 -0
  8. data/lib/protobuf/active_record/mass_assignment_security.rb +16 -0
  9. data/lib/protobuf/active_record/mass_assignment_security/persistence.rb +57 -0
  10. data/lib/protobuf/active_record/mass_assignment_security/transformation.rb +27 -0
  11. data/lib/protobuf/active_record/model.rb +32 -0
  12. data/lib/protobuf/active_record/persistence.rb +55 -0
  13. data/lib/protobuf/active_record/railtie.rb +11 -0
  14. data/lib/protobuf/active_record/scope.rb +121 -0
  15. data/lib/protobuf/active_record/serialization.rb +194 -0
  16. data/lib/protobuf/active_record/transformation.rb +155 -0
  17. data/lib/protobuf/active_record/validations.rb +43 -0
  18. data/lib/protobuf/{activerecord → active_record}/version.rb +1 -1
  19. data/protobuf-activerecord.gemspec +4 -5
  20. data/spec/{protoable → protobuf/active_record}/columns_spec.rb +2 -2
  21. data/spec/{protoable → protobuf/active_record}/persistence_spec.rb +1 -1
  22. data/spec/{protoable → protobuf/active_record}/scope_spec.rb +1 -8
  23. data/spec/{protoable → protobuf/active_record}/serialization_spec.rb +2 -2
  24. data/spec/{protoable → protobuf/active_record}/transformation_spec.rb +12 -12
  25. data/spec/support/models/user.rb +1 -1
  26. metadata +59 -68
  27. data/lib/protobuf/activerecord/protoable.rb +0 -19
  28. data/lib/protobuf/activerecord/protoable/columns.rb +0 -56
  29. data/lib/protobuf/activerecord/protoable/errors.rb +0 -22
  30. data/lib/protobuf/activerecord/protoable/persistence.rb +0 -56
  31. data/lib/protobuf/activerecord/protoable/scope.rb +0 -121
  32. data/lib/protobuf/activerecord/protoable/serialization.rb +0 -190
  33. data/lib/protobuf/activerecord/protoable/transformation.rb +0 -158
  34. data/lib/protobuf/activerecord/protoable/validations.rb +0 -39
  35. 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,5 +1,5 @@
1
1
  module Protobuf
2
2
  module ActiveRecord
3
- VERSION = "2.1.0"
3
+ VERSION = "3.0.0.pre"
4
4
  end
5
5
  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/activerecord/version'
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", "~> 3.1"
24
- gem.add_dependency "activesupport", "~> 3.1"
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.1.3"
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 Protoable::Columns do
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 Protoable::Persistence do
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' } }