protobuf-activerecord 1.2.6 → 2.0.0.beta

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