protobuf-activerecord 1.2.6 → 2.0.0.beta

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/Gemfile CHANGED
@@ -6,7 +6,3 @@ gemspec
6
6
  gem 'builder', '~> 3.0.4' # Builder 3.1.x is not supported by Active Record
7
7
  # and Bundler has trouble resolving the dependency
8
8
  # with Geminabox.
9
-
10
- gem 'timecop', '~> 0.3.5', :group => :development # Timecop 0.4.x is has a bug
11
- # when dealing with timezones
12
- # on Time objects.
@@ -1,17 +1,17 @@
1
- require 'protobuf/activerecord/protoable/convert'
1
+ require 'protobuf/activerecord/protoable/columns'
2
2
  require 'protobuf/activerecord/protoable/errors'
3
- require 'protobuf/activerecord/protoable/fields'
4
3
  require 'protobuf/activerecord/protoable/persistence'
5
4
  require 'protobuf/activerecord/protoable/scope'
6
5
  require 'protobuf/activerecord/protoable/serialization'
6
+ require 'protobuf/activerecord/protoable/transformation'
7
7
 
8
8
  module Protoable
9
9
  def self.included(klass)
10
- klass.extend Protoable::Fields
11
10
  klass.extend Protoable::Scope
12
11
 
13
- klass.__send__(:include, Protoable::Convert)
12
+ klass.__send__(:include, Protoable::Columns)
14
13
  klass.__send__(:include, Protoable::Persistence)
15
14
  klass.__send__(:include, Protoable::Serialization)
15
+ klass.__send__(:include, Protoable::Transformation)
16
16
  end
17
17
  end
@@ -0,0 +1,56 @@
1
+ require 'heredity/inheritable_class_instance_variables'
2
+
3
+ module Protoable
4
+ module Columns
5
+ def self.included(klass)
6
+ klass.extend Protoable::Columns::ClassMethods
7
+ klass.__send__(:include, ::Heredity::InheritableClassInstanceVariables)
8
+
9
+ klass.class_eval do
10
+ class << self
11
+ attr_accessor :_protobuf_columns, :_protobuf_column_types
12
+ end
13
+
14
+ @_protobuf_columns = {}
15
+ @_protobuf_column_types = Hash.new { |h,k| h[k] = [] }
16
+
17
+ # NOTE: Make sure each inherited object has the database layout
18
+ inheritable_attributes :_protobuf_columns, :_protobuf_column_types
19
+ end
20
+
21
+ _protobuf_map_columns(klass)
22
+ end
23
+
24
+ # Map out the columns for future reference on type conversion
25
+ #
26
+ def self._protobuf_map_columns(klass)
27
+ return unless klass.table_exists?
28
+ klass.columns.map do |column|
29
+ klass._protobuf_columns[column.name.to_sym] = column
30
+ klass._protobuf_column_types[column.type.to_sym] << column.name.to_sym
31
+ end
32
+ end
33
+
34
+ module ClassMethods
35
+ # :nodoc:
36
+ def _protobuf_date_column?(key)
37
+ _protobuf_column_types.fetch(:date, false) && _protobuf_column_types[:date].include?(key)
38
+ end
39
+
40
+ # :nodoc:
41
+ def _protobuf_datetime_column?(key)
42
+ _protobuf_column_types.fetch(:datetime, false) && _protobuf_column_types[:datetime].include?(key)
43
+ end
44
+
45
+ # :nodoc:
46
+ def _protobuf_time_column?(key)
47
+ _protobuf_column_types.fetch(:time, false) && _protobuf_column_types[:time].include?(key)
48
+ end
49
+
50
+ # :nodoc:
51
+ def _protobuf_timestamp_column?(key)
52
+ _protobuf_column_types.fetch(:timestamp, false) && _protobuf_column_types[:timestamp].include?(key)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -6,21 +6,11 @@ module Protoable
6
6
  class ProtoableError < StandardError
7
7
  end
8
8
 
9
- # Raised by Protoable.protoable_attribute when the convert method given is
10
- # not callable.
11
- class AttributeConverterError < ProtoableError
12
- end
13
-
14
9
  # Raised by Protoable.attribute_from_proto when the transformer method
15
10
  # given is not callable.
16
11
  class AttributeTransformerError < ProtoableError
17
12
  end
18
13
 
19
- # Raised by Protoable.convert_field when the convert method
20
- # given not callable.
21
- class FieldConverterError < ProtoableError
22
- end
23
-
24
14
  # Raised by Protoable.field_from_record when the convert method
25
15
  # given not callable.
26
16
  class FieldTransformerError < ProtoableError
@@ -17,62 +17,6 @@ module Protoable
17
17
  end
18
18
 
19
19
  module ClassMethods
20
- # Filters accessible attributes that exist in the given protobuf message's
21
- # fields or have attribute transformers defined for them.
22
- #
23
- # Returns a hash of attribute fields with their respective values.
24
- #
25
- def _filter_attribute_fields(proto)
26
- fields = proto.to_hash
27
- fields.select! { |key, value| proto.has_field?(key) && !proto.get_field_by_name(key).repeated? }
28
-
29
- attribute_fields = _filtered_attributes.inject({}) do |hash, column_name|
30
- symbolized_column = column_name.to_sym
31
-
32
- if fields.has_key?(symbolized_column) ||
33
- _protobuf_attribute_transformers.has_key?(symbolized_column)
34
- hash[symbolized_column] = fields[symbolized_column]
35
- end
36
-
37
- hash
38
- end
39
-
40
- attribute_fields
41
- end
42
-
43
- # Filters protected attributes from the available attributes list. When
44
- # set through accessible attributes, returns the accessible attributes.
45
- # When set through protected attributes, returns the attributes minus any
46
- # protected attributes.
47
- #
48
- def _filtered_attributes
49
- return accessible_attributes.to_a if accessible_attributes.present?
50
-
51
- return self.attribute_names - protected_attributes.to_a
52
- end
53
-
54
- # Creates a hash of attributes from a given protobuf message.
55
- #
56
- # It converts and transforms field values using the field converters and
57
- # attribute transformers, ignoring repeated and nil fields.
58
- #
59
- def attributes_from_proto(proto)
60
- attribute_fields = _filter_attribute_fields(proto)
61
-
62
- attributes = attribute_fields.inject({}) do |hash, (key, value)|
63
- if _protobuf_attribute_transformers.has_key?(key)
64
- attribute = _protobuf_attribute_transformers[key].call(proto)
65
- hash[key] = attribute unless attribute.nil?
66
- else
67
- hash[key] = _protobuf_convert_fields_to_columns(key, value)
68
- end
69
-
70
- hash
71
- end
72
-
73
- attributes
74
- end
75
-
76
20
  # :nodoc:
77
21
  def create(attributes, options = {}, &block)
78
22
  attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
@@ -86,17 +30,6 @@ module Protoable
86
30
 
87
31
  super(attributes, options)
88
32
  end
89
-
90
- # Creates an object from the given protobuf message, if it's valid. The
91
- # newly created object is returned if it was successfully saved or not.
92
- #
93
- def create_from_proto(proto, options = {})
94
- attributes = attributes_from_proto(proto)
95
-
96
- yield(attributes) if block_given?
97
-
98
- self.create(attributes, options)
99
- end
100
33
  end
101
34
 
102
35
  # :nodoc:
@@ -106,19 +39,6 @@ module Protoable
106
39
  super(attributes, options)
107
40
  end
108
41
 
109
- # Calls up to the class version of the method.
110
- #
111
- def attributes_from_proto(proto)
112
- self.class.attributes_from_proto(proto)
113
- end
114
-
115
- # Destroys the record. Mainly wrapped to provide a consistent API and
116
- # a convient way to override protobuf-specific destroy behavior.
117
- #
118
- def destroy_from_proto
119
- destroy
120
- end
121
-
122
42
  # :nodoc:
123
43
  def update_attributes(attributes, options = {})
124
44
  attributes = attributes_from_proto(attributes) if attributes.is_a?(::Protobuf::Message)
@@ -132,16 +52,5 @@ module Protoable
132
52
 
133
53
  super(attributes, options)
134
54
  end
135
-
136
- # Update a record from a proto message. Accepts an optional block.
137
- # If block is given, yields the attributes that would be updated.
138
- #
139
- def update_from_proto(proto, options = {})
140
- attributes = attributes_from_proto(proto)
141
-
142
- yield(attributes) if block_given?
143
-
144
- update_attributes(attributes, options)
145
- end
146
55
  end
147
56
  end
@@ -4,7 +4,6 @@ module Protoable
4
4
  klass.class_eval do
5
5
  class << self
6
6
  alias_method :by_fields, :search_scope
7
- alias_method :from_proto, :search_scope
8
7
  alias_method :scope_from_proto, :search_scope
9
8
  end
10
9
  end
@@ -34,7 +33,6 @@ module Protoable
34
33
  # # Search starting with the default scope and searchable fields
35
34
  # User.search_scope(request)
36
35
  # User.by_fields(request)
37
- # User.from_proto(request)
38
36
  # User.scope_from_proto(request)
39
37
  #
40
38
  def search_scope(proto)
@@ -8,20 +8,38 @@ module Protoable
8
8
 
9
9
  klass.class_eval do
10
10
  class << self
11
- attr_accessor :_protobuf_attribute_converters,
12
- :_protobuf_field_transformers, :protobuf_fields
11
+ attr_accessor :_protobuf_field_transformers, :_protobuf_field_options
13
12
  end
14
13
 
15
- @_protobuf_attribute_converters = {}
16
14
  @_protobuf_field_transformers = {}
17
- @protobuf_fields = []
15
+ @_protobuf_field_options = {}
18
16
 
19
- inheritable_attributes :_protobuf_attribute_converters,
20
- :_protobuf_field_transformers, :protobuf_fields, :protobuf_message
17
+ inheritable_attributes :_protobuf_field_transformers, :_protobuf_field_options,
18
+ :protobuf_message
21
19
  end
22
20
  end
23
21
 
24
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
+
25
43
  # Define a field transformation from a record. Accepts a Symbol,
26
44
  # callable, or block that is called with the record being serialized.
27
45
  #
@@ -39,8 +57,8 @@ module Protoable
39
57
  # # Do some blocky stuff...
40
58
  # end
41
59
  #
42
- def field_from_record(field, transformer = nil, &blk)
43
- transformer ||= blk
60
+ def field_from_record(field, transformer = nil, &block)
61
+ transformer ||= block
44
62
 
45
63
  if transformer.is_a?(Symbol)
46
64
  callable = lambda { |value| self.__send__(transformer, value) }
@@ -55,68 +73,37 @@ module Protoable
55
73
  _protobuf_field_transformers[field.to_sym] = callable
56
74
  end
57
75
 
58
- # Define a custom attribute conversion for serialization to protobuf.
59
- # Accepts a Symbol, Hash, callable or block.
60
- #
61
- # When given a callable or block, it is directly used to convert the field.
62
- #
63
- # When a Hash is given, :from and :to keys are expected and expand
64
- # to extracting a class method in the format of
65
- # "convert_#{from}_to_#{to}".
66
- #
67
- # When a symbol is given, it extracts the method with the same name.
68
- #
69
- # Examples:
70
- # protoable_attribute :public_key, :extract_public_key_from_proto
71
- # protoable_attribute :symmetric_key, :from => :base64, :to => :raw_string
72
- # protoable_attribute :status, lambda { |proto_field| # Do stuff... }
73
- # protoable_attribute :status do |proto_field|
74
- # # Do some blocky stuff...
75
- # end
76
- #
77
- def protoable_attribute(field, converter = nil, &blk)
78
- converter ||= blk
79
- converter = :"convert_#{converter[:from]}_to_#{converter[:to]}" if converter.is_a?(Hash)
80
-
81
- if converter.is_a?(Symbol)
82
- callable = lambda { |value| __send__(converter, value) }
83
- else
84
- callable = converter
85
- end
86
-
87
- unless callable.respond_to?(:call)
88
- raise AttributeConverterError, 'Attribute converters must be a callable or block!'
89
- end
90
-
91
- _protobuf_attribute_converters[field.to_sym] = callable
92
- end
93
-
94
76
  # Define the protobuf message class that should be used to serialize the
95
- # object to protobuf. Accepts a string or symbol.
77
+ # object to protobuf. Accepts a string or symbol and an options hash.
96
78
  #
97
79
  # When protobuf_message is declared, Protoable automatically extracts the
98
- # fields from the message and automatically adds to_proto and to_proto_hash
99
- # methods that serialize the object to protobuf.
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.
100
90
  #
101
91
  # Examples:
102
92
  # protobuf_message :user_message
103
93
  # protobuf_message "UserMessage"
104
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
105
98
  #
106
- def protobuf_message(message = nil)
99
+ def protobuf_message(message = nil, options = {})
107
100
  unless message.nil?
108
101
  @protobuf_message = message.to_s.classify.constantize
109
102
 
110
- self.protobuf_fields = @protobuf_message.fields.compact.map do |field|
111
- field.name.to_sym
112
- end
113
-
114
- define_method(:to_proto) do
115
- self.class.protobuf_message.new(self.to_proto_hash)
116
- end
103
+ self._protobuf_field_options = options
117
104
 
118
- define_method(:to_proto_hash) do
119
- protoable_attributes
105
+ define_method(:to_proto) do |options = {}|
106
+ self.class.protobuf_message.new(self.fields_from_record(options))
120
107
  end
121
108
  end
122
109
 
@@ -124,11 +111,51 @@ module Protoable
124
111
  end
125
112
  end
126
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.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
+
127
148
  # Extracts attributes that correspond to fields on the specified protobuf
128
149
  # message, performing any necessary column conversions on them.
129
150
  #
130
- def protoable_attributes
131
- protoable_attributes = protobuf_fields.inject({}) do |hash, field|
151
+ def fields_from_record(options = {})
152
+ field_attributes = _filter_field_attributes(options)
153
+ field_attributes += [ options.fetch(:include, []) ]
154
+ field_attributes.flatten!
155
+ field_attributes.compact
156
+ field_attributes.uniq!
157
+
158
+ field_attributes = field_attributes.inject({}) do |hash, field|
132
159
  if _protobuf_field_transformers.has_key?(field)
133
160
  hash[field] = _protobuf_field_transformers[field].call(self)
134
161
  else
@@ -138,7 +165,7 @@ module Protoable
138
165
  hash
139
166
  end
140
167
 
141
- protoable_attributes
168
+ field_attributes
142
169
  end
143
170
 
144
171
  private
@@ -150,9 +177,5 @@ module Protoable
150
177
  def _protobuf_field_transformers
151
178
  self.class._protobuf_field_transformers
152
179
  end
153
-
154
- def protobuf_fields
155
- self.class.protobuf_fields
156
- end
157
180
  end
158
181
  end