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
@@ -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