protobuf-activerecord 2.1.0 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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' } }