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
@@ -1,22 +0,0 @@
1
- module Protoable
2
-
3
- # = Protoable errors
4
- #
5
- # Generic Protoable exception class
6
- class ProtoableError < StandardError
7
- end
8
-
9
- # Raised by Protoable.attribute_from_proto when the transformer method
10
- # given is not callable.
11
- class AttributeTransformerError < ProtoableError
12
- end
13
-
14
- # Raised by Protoable.field_from_record when the convert method
15
- # given not callable.
16
- class FieldTransformerError < ProtoableError
17
- end
18
-
19
- # Raised by Protoable.field_scope when given scope is not defined.
20
- class SearchScopeError < ProtoableError
21
- end
22
- end
@@ -1,56 +0,0 @@
1
- module Protoable
2
- module Persistence
3
- def self.included(klass)
4
- klass.extend Protoable::Persistence::ClassMethods
5
-
6
- klass.class_eval do
7
- # Override Active Record's initialize method so it can accept a protobuf
8
- # message as it's attributes. Need to do it in class_eval block since initialize
9
- # is defined in ActiveRecord::Base.
10
- # :noapi:
11
- def initialize(*args)
12
- args[0] = attributes_from_proto(args.first) if args.first.is_a?(::Protobuf::Message)
13
-
14
- super(*args)
15
- end
16
- end
17
- end
18
-
19
- module ClassMethods
20
- # :nodoc:
21
- def create(attributes, options = {}, &block)
22
- attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
23
-
24
- super(attributes, options)
25
- end
26
-
27
- # :nodoc:
28
- def create!(attributes, options = {}, &block)
29
- attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
30
-
31
- super(attributes, options)
32
- end
33
- end
34
-
35
- # :nodoc:
36
- def assign_attributes(attributes, options = {})
37
- attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
38
-
39
- super(attributes, options)
40
- end
41
-
42
- # :nodoc:
43
- def update_attributes(attributes, options = {})
44
- attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
45
-
46
- super(attributes, options)
47
- end
48
-
49
- # :nodoc:
50
- def update_attributes!(attributes, options = {})
51
- attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
52
-
53
- super(attributes, options)
54
- end
55
- end
56
- end
@@ -1,121 +0,0 @@
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 :scope_from_proto, :search_scope
8
- end
9
- end
10
- end
11
-
12
- # Define fields that should be searchable via `search_scope`. Accepts a
13
- # protobuf field and an already defined scope. If no scope is specified,
14
- # the scope will be the field name, prefixed with `by_` (e.g. when the
15
- # field is :guid, the scope will be :by_guid).
16
- #
17
- # Optionally, a parser can be provided that will be called, passing the
18
- # field value as an argument. This allows custom data parsers to be used
19
- # so that they don't have to be handled by scopes. Parsers can be procs,
20
- # lambdas, or symbolized method names and must accept the value of the
21
- # field as a parameter.
22
- #
23
- # > **Deprecated usage**: Previous versions required the scope to be passed
24
- # as a second argument. This has been replaced with the new hash-style
25
- # options or simply relying on the defaults. While it will still work
26
- # until v3.0 is released, it has been deprecated.
27
- #
28
- #
29
- # Examples:
30
- #
31
- # class User < ActiveRecord::Base
32
- # scope :by_guid, lambda { |*guids| where(:guid => guids) }
33
- # scope :custom_guid_scope, lambda { |*guids| where(:guid => guids) }
34
- #
35
- # # Equivalent to `field_scope :guid, :by_guid`
36
- # field_scope :guid
37
- #
38
- # # With a custom scope
39
- # field_scope :guid, :scope => :custom_guid_scope
40
- #
41
- # # With a custom parser that converts the value to an integer
42
- # field_scope :guid, :scope => :custom_guid_scope, :parser => lambda { |value| value.to_i }
43
- # end
44
- #
45
- def field_scope(field, *args)
46
- # TODO: For backwards compatibility. Remove this in the next major release.
47
- options = args.extract_options!
48
-
49
- scope_name = case
50
- when args.present? then
51
- warn "WARNING: Passing scopes directly to field_scope is deprecated and will be removed in 3.0. Use :scope => xxx instead."
52
- args.first
53
- when options.include?(:scope) then
54
- options[:scope]
55
- else
56
- # When no scope is defined, assume the scope is the field, prefixed with `by_`
57
- :"by_#{field}"
58
- end
59
- searchable_fields[field] = scope_name
60
-
61
- searchable_field_parsers[field] = options[:parser] if options[:parser]
62
- end
63
-
64
- # :noapi:
65
- def parse_search_values(proto, field)
66
- value = proto.__send__(field)
67
-
68
- if searchable_field_parsers[field]
69
- parser = searchable_field_parsers[field]
70
-
71
- if parser.respond_to?(:to_sym)
72
- value = self.__send__(parser.to_sym, value)
73
- else
74
- value = parser.call(value)
75
- end
76
- end
77
-
78
- values = [ value ].flatten
79
- values.map!(&:to_i) if proto.get_field_by_name(field).enum?
80
- values
81
- end
82
-
83
- # Builds and returns a Arel relation based on the fields that are present
84
- # in the given protobuf message using the searchable fields to determine
85
- # what scopes to use. Provides several aliases for variety.
86
- #
87
- # Examples:
88
- #
89
- # # Search starting with the default scope and searchable fields
90
- # User.search_scope(request)
91
- # User.by_fields(request)
92
- # User.scope_from_proto(request)
93
- #
94
- def search_scope(proto)
95
- relation = scoped # Get an ARel relation to build off of
96
-
97
- searchable_fields.each do |field, scope_name|
98
- next unless proto.respond_to_and_has_and_present?(field)
99
-
100
- unless self.respond_to?(scope_name)
101
- raise Protoable::SearchScopeError, "Undefined scope :#{scope_name}."
102
- end
103
-
104
- search_values = parse_search_values(proto, field)
105
- relation = relation.__send__(scope_name, *search_values)
106
- end
107
-
108
- return relation
109
- end
110
-
111
- # :noapi:
112
- def searchable_fields
113
- @_searchable_fields ||= {}
114
- end
115
-
116
- # :noapi:
117
- def searchable_field_parsers
118
- @_searchable_field_parsers ||= {}
119
- end
120
- end
121
- end
@@ -1,190 +0,0 @@
1
- require 'heredity/inheritable_class_instance_variables'
2
-
3
- module Protoable
4
- module Serialization
5
- def self.included(klass)
6
- klass.extend Protoable::Serialization::ClassMethods
7
- klass.__send__(:include, ::Heredity::InheritableClassInstanceVariables)
8
-
9
- klass.class_eval do
10
- class << self
11
- attr_accessor :_protobuf_field_transformers, :_protobuf_field_options
12
- end
13
-
14
- @_protobuf_field_transformers = {}
15
- @_protobuf_field_options = {}
16
-
17
- inheritable_attributes :_protobuf_field_transformers, :_protobuf_field_options,
18
- :protobuf_message
19
- end
20
- end
21
-
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
-
43
- # Define a field transformation from a record. Accepts a Symbol,
44
- # callable, or block that is called with the record being serialized.
45
- #
46
- # When given a callable or block, it is directly used to convert the field.
47
- #
48
- # When a symbol is given, it extracts the method with the same name.
49
- #
50
- # The callable or method must accept a single parameter, which is the
51
- # proto message.
52
- #
53
- # Examples:
54
- # field_from_record :public_key, :convert_public_key_to_proto
55
- # field_from_record :status, lambda { |record| # Do some stuff... }
56
- # field_from_record :status do |record|
57
- # # Do some blocky stuff...
58
- # end
59
- #
60
- def field_from_record(field, transformer = nil, &block)
61
- transformer ||= block
62
-
63
- if transformer.is_a?(Symbol)
64
- callable = lambda { |value| self.__send__(transformer, value) }
65
- else
66
- callable = transformer
67
- end
68
-
69
- unless callable.respond_to?(:call)
70
- raise FieldTransformerError, 'Attribute transformers need a callable or block!'
71
- end
72
-
73
- _protobuf_field_transformers[field.to_sym] = callable
74
- end
75
-
76
- # Define the protobuf message class that should be used to serialize the
77
- # object to protobuf. Accepts a string or symbol and an options hash.
78
- #
79
- # When protobuf_message is declared, Protoable automatically extracts the
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.
90
- #
91
- # Examples:
92
- # protobuf_message :user_message
93
- # protobuf_message "UserMessage"
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
98
- #
99
- def protobuf_message(message = nil, options = {})
100
- unless message.nil?
101
- @protobuf_message = message.to_s.classify.constantize
102
-
103
- self._protobuf_field_options = options
104
-
105
- define_method(:to_proto) do |options = {}|
106
- self.class.protobuf_message.new(self.fields_from_record(options))
107
- end
108
- end
109
-
110
- @protobuf_message
111
- end
112
- end
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.all_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
-
148
- # Extracts attributes that correspond to fields on the specified protobuf
149
- # message, performing any necessary column conversions on them. Accepts a
150
- # hash of options for specifying which fields should be serialized.
151
- #
152
- # Examples:
153
- # fields_from_record(:only => [ :guid, :name ])
154
- # fields_from_record(:except => :email_domain)
155
- # fields_from_record(:include => :email_domain)
156
- # fields_from_record(:except => :email_domain, :deprecated => false)
157
- #
158
- def fields_from_record(options = {})
159
- field_attributes = _filter_field_attributes(options)
160
- field_attributes += [ options.fetch(:include, []) ]
161
- field_attributes.flatten!
162
- field_attributes.compact!
163
- field_attributes.uniq!
164
-
165
- field_attributes = field_attributes.inject({}) do |hash, field|
166
- if _protobuf_field_transformers.has_key?(field)
167
- hash[field] = _protobuf_field_transformers[field].call(self)
168
- else
169
- value = respond_to?(field) ? __send__(field) : nil
170
- hash[field] = _protobuf_convert_attributes_to_fields(field, value)
171
- end
172
- hash
173
- end
174
-
175
- field_attributes
176
- end
177
-
178
- private
179
-
180
- # :nodoc:
181
- def _protobuf_convert_attributes_to_fields(field, value)
182
- self.class._protobuf_convert_attributes_to_fields(field, value)
183
- end
184
-
185
- # :nodoc:
186
- def _protobuf_field_transformers
187
- self.class._protobuf_field_transformers
188
- end
189
- end
190
- end
@@ -1,158 +0,0 @@
1
- require 'heredity/inheritable_class_instance_variables'
2
-
3
- module Protoable
4
- module Transformation
5
- def self.included(klass)
6
- klass.extend Protoable::Transformation::ClassMethods
7
- klass.__send__(:include, ::Heredity::InheritableClassInstanceVariables)
8
-
9
- klass.class_eval do
10
- class << self
11
- attr_accessor :_protobuf_attribute_transformers
12
- end
13
-
14
- @_protobuf_attribute_transformers = {}
15
-
16
- inheritable_attributes :_protobuf_attribute_transformers
17
- end
18
- end
19
-
20
- module ClassMethods
21
- # Filters accessible attributes that exist in the given protobuf message's
22
- # fields or have attribute transformers defined for them.
23
- #
24
- # Returns a hash of attribute fields with their respective values.
25
- #
26
- # :nodoc:
27
- def _filter_attribute_fields(proto)
28
- fields = proto.to_hash
29
- fields.select! do |key, value|
30
- field = proto.get_field_by_name(key) || proto.get_ext_field_by_name(key)
31
- proto.has_field?(key) && !field.repeated?
32
- end
33
-
34
- attribute_fields = _filtered_attributes.inject({}) do |hash, column_name|
35
- symbolized_column = column_name.to_sym
36
-
37
- if fields.has_key?(symbolized_column) ||
38
- _protobuf_attribute_transformers.has_key?(symbolized_column)
39
- hash[symbolized_column] = fields[symbolized_column]
40
- end
41
-
42
- hash
43
- end
44
-
45
- attribute_fields
46
- end
47
-
48
- # Filters protected attributes from the available attributes list. When
49
- # set through accessible attributes, returns the accessible attributes.
50
- # When set through protected attributes, returns the attributes minus any
51
- # protected attributes.
52
- #
53
- # :nodoc:
54
- def _filtered_attributes
55
- return accessible_attributes.to_a if accessible_attributes.present?
56
-
57
- return self.attribute_names - protected_attributes.to_a
58
- end
59
-
60
- # :nodoc:
61
- def _protobuf_convert_fields_to_columns(key, value)
62
- return value if value.nil?
63
-
64
- value = case
65
- when _protobuf_date_column?(key) then
66
- convert_int64_to_date(value)
67
- when _protobuf_datetime_column?(key) then
68
- convert_int64_to_datetime(value)
69
- when _protobuf_time_column?(key) then
70
- convert_int64_to_time(value)
71
- when _protobuf_timestamp_column?(key) then
72
- convert_int64_to_time(value)
73
- else
74
- value
75
- end
76
-
77
- return value
78
- end
79
-
80
- # Define an attribute transformation from protobuf. Accepts a Symbol,
81
- # callable, or block.
82
- #
83
- # When given a callable or block, it is directly used to convert the field.
84
- #
85
- # When a symbol is given, it extracts the method with the same name.
86
- #
87
- # The callable or method must accept a single parameter, which is the
88
- # proto message.
89
- #
90
- # Examples:
91
- # attribute_from_proto :public_key, :extract_public_key_from_proto
92
- # attribute_from_proto :status, lambda { |proto| # Do some stuff... }
93
- # attribute_from_proto :status do |proto|
94
- # # Do some blocky stuff...
95
- # end
96
- #
97
- def attribute_from_proto(attribute, transformer = nil, &block)
98
- transformer ||= block
99
-
100
- if transformer.is_a?(Symbol)
101
- callable = lambda { |value| self.__send__(transformer, value) }
102
- else
103
- callable = transformer
104
- end
105
-
106
- unless callable.respond_to?(:call)
107
- raise AttributeTransformerError, 'Attribute transformers need a callable or block!'
108
- end
109
-
110
- _protobuf_attribute_transformers[attribute.to_sym] = callable
111
- end
112
-
113
-
114
- # Creates a hash of attributes from a given protobuf message.
115
- #
116
- # It converts and transforms field values using the field converters and
117
- # attribute transformers, ignoring repeated and nil fields.
118
- #
119
- def attributes_from_proto(proto)
120
- attribute_fields = _filter_attribute_fields(proto)
121
-
122
- attributes = attribute_fields.inject({}) do |hash, (key, value)|
123
- if _protobuf_attribute_transformers.has_key?(key)
124
- attribute = _protobuf_attribute_transformers[key].call(proto)
125
- hash[key] = attribute unless attribute.nil?
126
- else
127
- hash[key] = _protobuf_convert_fields_to_columns(key, value)
128
- end
129
-
130
- hash
131
- end
132
-
133
- attributes
134
- end
135
-
136
- # :nodoc:
137
- def convert_int64_to_time(int64)
138
- Time.at(int64.to_i)
139
- end
140
-
141
- # :nodoc:
142
- def convert_int64_to_date(int64)
143
- convert_int64_to_time(int64).utc.to_date
144
- end
145
-
146
- # :nodoc:
147
- def convert_int64_to_datetime(int64)
148
- convert_int64_to_time(int64).to_datetime
149
- end
150
- end
151
-
152
- # Calls up to the class version of the method.
153
- #
154
- def attributes_from_proto(proto)
155
- self.class.attributes_from_proto(proto)
156
- end
157
- end
158
- end