massive_record 0.2.0 → 0.2.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGELOG.md +43 -4
  2. data/Gemfile.lock +3 -1
  3. data/README.md +5 -0
  4. data/lib/massive_record/adapters/thrift/connection.rb +23 -16
  5. data/lib/massive_record/adapters/thrift/row.rb +13 -33
  6. data/lib/massive_record/adapters/thrift/table.rb +24 -10
  7. data/lib/massive_record/orm/attribute_methods.rb +27 -1
  8. data/lib/massive_record/orm/attribute_methods/dirty.rb +2 -2
  9. data/lib/massive_record/orm/attribute_methods/read.rb +36 -1
  10. data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +81 -0
  11. data/lib/massive_record/orm/attribute_methods/write.rb +18 -0
  12. data/lib/massive_record/orm/base.rb +52 -10
  13. data/lib/massive_record/orm/callbacks.rb +1 -1
  14. data/lib/massive_record/orm/default_id.rb +20 -0
  15. data/lib/massive_record/orm/errors.rb +4 -0
  16. data/lib/massive_record/orm/finders.rb +102 -57
  17. data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +45 -0
  18. data/lib/massive_record/orm/id_factory.rb +1 -1
  19. data/lib/massive_record/orm/log_subscriber.rb +85 -0
  20. data/lib/massive_record/orm/persistence.rb +82 -37
  21. data/lib/massive_record/orm/query_instrumentation.rb +64 -0
  22. data/lib/massive_record/orm/relations/interface.rb +10 -0
  23. data/lib/massive_record/orm/relations/metadata.rb +2 -0
  24. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
  25. data/lib/massive_record/orm/schema/field.rb +33 -6
  26. data/lib/massive_record/orm/timestamps.rb +1 -1
  27. data/lib/massive_record/orm/validations.rb +2 -2
  28. data/lib/massive_record/rails/controller_runtime.rb +55 -0
  29. data/lib/massive_record/rails/railtie.rb +16 -0
  30. data/lib/massive_record/version.rb +1 -1
  31. data/lib/massive_record/wrapper/cell.rb +32 -3
  32. data/massive_record.gemspec +1 -0
  33. data/spec/{wrapper/cases → adapter/thrift}/adapter_spec.rb +0 -0
  34. data/spec/adapter/thrift/atomic_increment_spec.rb +55 -0
  35. data/spec/{wrapper/cases → adapter/thrift}/connection_spec.rb +0 -10
  36. data/spec/adapter/thrift/table_find_spec.rb +40 -0
  37. data/spec/{wrapper/cases → adapter/thrift}/table_spec.rb +55 -13
  38. data/spec/orm/cases/attribute_methods_spec.rb +6 -1
  39. data/spec/orm/cases/base_spec.rb +18 -4
  40. data/spec/orm/cases/callbacks_spec.rb +1 -1
  41. data/spec/orm/cases/default_id_spec.rb +38 -0
  42. data/spec/orm/cases/default_values_spec.rb +37 -0
  43. data/spec/orm/cases/dirty_spec.rb +25 -1
  44. data/spec/orm/cases/encoding_spec.rb +3 -3
  45. data/spec/orm/cases/finder_default_scope.rb +8 -1
  46. data/spec/orm/cases/finder_scope_spec.rb +2 -2
  47. data/spec/orm/cases/finders_spec.rb +8 -18
  48. data/spec/orm/cases/id_factory_spec.rb +38 -21
  49. data/spec/orm/cases/log_subscriber_spec.rb +133 -0
  50. data/spec/orm/cases/mass_assignment_security_spec.rb +97 -0
  51. data/spec/orm/cases/persistence_spec.rb +132 -27
  52. data/spec/orm/cases/single_table_inheritance_spec.rb +2 -2
  53. data/spec/orm/cases/time_zone_awareness_spec.rb +157 -0
  54. data/spec/orm/cases/timestamps_spec.rb +15 -0
  55. data/spec/orm/cases/validation_spec.rb +2 -2
  56. data/spec/orm/models/model_without_default_id.rb +5 -0
  57. data/spec/orm/models/person.rb +1 -0
  58. data/spec/orm/models/test_class.rb +1 -0
  59. data/spec/orm/relations/interface_spec.rb +2 -2
  60. data/spec/orm/relations/metadata_spec.rb +1 -1
  61. data/spec/orm/relations/proxy/references_many_spec.rb +21 -15
  62. data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +7 -1
  63. data/spec/orm/relations/proxy/references_one_spec.rb +7 -0
  64. data/spec/orm/schema/field_spec.rb +61 -5
  65. data/spec/support/connection_helpers.rb +2 -1
  66. data/spec/support/mock_massive_record_connection.rb +7 -0
  67. data/spec/support/time_zone_helper.rb +25 -0
  68. metadata +51 -14
@@ -1,7 +1,46 @@
1
- # v0.2.1 (git develop)
2
-
3
-
4
- # v0.2.0 (git master)
1
+ # v0.2.2 (git develop)
2
+
3
+ # v0.2.1.rc1 (git master)
4
+
5
+ - Models without any default_id will now by default get an id via next_id(). You can turn it off
6
+ via the setting auto_increment_id on ORM::Base or on the Model class itself.
7
+ - record.reload now resets relations.
8
+ - If you have a persisted record and you set one attribute to nil that attribute will be
9
+ deleted from HBase to represent the nil-value. The fact that the schema of that record
10
+ class knows of that attribute will be the reason for it to still respond and return nil.
11
+ - Thrift adapter will no longer return UTF-8 encoded strings. The reason for this is that
12
+ binary representation of integers cannot be set with a UTF-8 encoding; it must be BINARY.
13
+ It is the client`s responsibility of the adapter to set correct encoding, and the ORM now
14
+ does this too.
15
+ - If you do have a database where integers are stored as string, you should enable
16
+ ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings. It will, before for
17
+ instance atomic_increment! is called, ensure that the integers has been persisted as hex.
18
+ - Based on the previous change we are now able to do real atomic incrementation of integer values.
19
+ HBase will do the incrementation and guarantee the integrity of that incrementation.
20
+ - Fixnum and Bignum are stored as a 64 bit signed integer representation, not as strings. Fixnum and bignum
21
+ values are no longer encoded by the ORM; that responsibility has been moved down to the adapter.
22
+ - We can now, by setting Base.check_record_uniqueness_on_create to true, do a quick and simple (and
23
+ kinda insecure) check if the record id exists on a new_record when doing a create call. Just a simple sanity check.
24
+ - We now have mass assignment of attributes, just like ActiveRecord. It uses the same module,
25
+ so attr_accessible and attr_protected methods are available. By default the id and inheritable attribute
26
+ are protected. If you where doing Person.new(:id => 'ID') you might want to change this now as it will no
27
+ longer work.
28
+ - Ids can now be assigned on new/create/create! with: Person.new("id", attributes). Person.new(id: "id") will
29
+ is soon to be disallowed.
30
+ - Polymorphic type attribute is no longer called underscore on. Should be backwards compatible when finding records.
31
+ - Time can now be time zone aware. Times are being presented in the Time.zone, and persisted
32
+ in UTC. You enable it with MassiveRecord::ORM::Base.time_zone_aware_attributes = true.
33
+ In a Rails application this will be set to true by default.
34
+ - No longer checking if table exist on a find. Instead we rescue error the few times a table does not exist,
35
+ and create it based on current schema. This gained a lot of speed on our production server.
36
+ - Changed the way Thrift::Table executes find(). It is now doing the work ~3 times faster.
37
+ - In Rails, we are now included in the 200-ok-completed-log like: Completed 200 OK in 798ms (Views: 277.5ms | MassiveRecord: 9.2ms)
38
+ - Subscribed to events from query instruments and printing out time spent in database per call.
39
+ - Added ActiveSupport Notifications instruments around database query calls in the ORM level.
40
+ - Removed inclusion of ActiveModel::Translation in ORM::Base class, as including ActiveModel::Validations
41
+ extends ORM::Base with Translation as well (and it should never have been included; it should have been extended with..)
42
+
43
+ # v0.2.0
5
44
 
6
45
  - Intersection and union operations on arrays containing MassiveRecord objects is now working as expected.
7
46
  - You can now disallow nil values, and in that case we will ensure that given field has its default value.
@@ -1,10 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- massive_record (0.2.0)
4
+ massive_record (0.2.1.rc1)
5
5
  activemodel
6
6
  activesupport
7
7
  thrift (>= 0.5.0)
8
+ tzinfo
8
9
 
9
10
  GEM
10
11
  remote: http://rubygems.org/
@@ -26,6 +27,7 @@ GEM
26
27
  diff-lcs (~> 1.1.2)
27
28
  rspec-mocks (2.5.0)
28
29
  thrift (0.6.0)
30
+ tzinfo (0.3.27)
29
31
 
30
32
  PLATFORMS
31
33
  ruby
data/README.md CHANGED
@@ -59,6 +59,9 @@ Both MassiveRecord::ORM::Table and MassiveRecord::ORM::Column do now have some f
59
59
  - Timestamps like created_at and updated_at. Updated at will always be available, created_at must be defined. See example down:
60
60
  - Finder scopes. Like: Person.select(:only_columns_from_this_family).limit(10).collect(&:name)
61
61
  - Ability to set a default scope.
62
+ - Time zone aware time attributes.
63
+ - Basic instrumentation and logging of query times.
64
+ - Attribute mass assignment security.
62
65
 
63
66
  Tables also have:
64
67
  - Persistencey method calls like create, save and destroy (but they do not actually save things to hbase)
@@ -98,6 +101,7 @@ Here is an example of usage, both for Table and Column:
98
101
  field :with_a_lot_of_uninteresting_data
99
102
  end
100
103
 
104
+ attr_accessible :name, :email, :phone_number, :date_of_birth
101
105
 
102
106
  validates_presence_of :name, :email
103
107
  validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
@@ -122,6 +126,7 @@ Here is an example of usage, both for Table and Column:
122
126
  field :nice_place, :boolean, :default => true
123
127
  end
124
128
 
129
+ You can find a small example application here: https://github.com/thhermansen/massive_record_test_app
125
130
 
126
131
  ### Related gems
127
132
 
@@ -1,3 +1,5 @@
1
+ require 'active_support/notifications'
2
+
1
3
  module MassiveRecord
2
4
  module Adapters
3
5
  module Thrift
@@ -9,21 +11,30 @@ module MassiveRecord
9
11
  @timeout = 4000
10
12
  @host = opts[:host]
11
13
  @port = opts[:port] || 9090
14
+ @instrumenter = ActiveSupport::Notifications.instrumenter
12
15
  end
13
16
 
14
17
  def transport
15
18
  @transport ||= ::Thrift::BufferedTransport.new(::Thrift::Socket.new(@host, @port, @timeout))
16
19
  end
17
20
 
18
- def open
19
- protocol = ::Thrift::BinaryProtocol.new(transport)
20
- @client = Apache::Hadoop::Hbase::Thrift::Hbase::Client.new(protocol)
21
-
22
- begin
23
- transport.open()
24
- true
25
- rescue
26
- raise MassiveRecord::Wrapper::Errors::ConnectionException.new, "Unable to connect to HBase on #{@host}, port #{@port}"
21
+ def open(options = {})
22
+ options = options.merge({
23
+ :adapter => 'Thrift',
24
+ :host => @host,
25
+ :port => @port
26
+ })
27
+
28
+ @instrumenter.instrument "adapter_connecting.massive_record", options do
29
+ protocol = ::Thrift::BinaryProtocol.new(transport)
30
+ @client = Apache::Hadoop::Hbase::Thrift::Hbase::Client.new(protocol)
31
+
32
+ begin
33
+ transport.open()
34
+ true
35
+ rescue
36
+ raise MassiveRecord::Wrapper::Errors::ConnectionException.new, "Unable to connect to HBase on #{@host}, port #{@port}"
37
+ end
27
38
  end
28
39
  end
29
40
 
@@ -55,14 +66,10 @@ module MassiveRecord
55
66
  begin
56
67
  open if not @client
57
68
  client.send(method, *args) if @client
58
- rescue IOError
59
- @client = nil
60
- open
61
- client.send(method, *args) if @client
62
- rescue ::Thrift::TransportException
69
+ rescue ::Thrift::TransportException => error
63
70
  @transport = nil
64
71
  @client = nil
65
- open
72
+ open(:reconnecting => true, :reason => error.class)
66
73
  client.send(method, *args) if @client
67
74
  end
68
75
  end
@@ -70,4 +77,4 @@ module MassiveRecord
70
77
  end
71
78
  end
72
79
  end
73
- end
80
+ end
@@ -69,46 +69,26 @@ module MassiveRecord
69
69
  mutations = []
70
70
 
71
71
  @columns.each do |column_name, cell|
72
- m = Apache::Hadoop::Hbase::Thrift::Mutation.new
73
- m.column = column_name
74
- m.value = cell.value_to_thrift
75
-
76
- mutations.push(m)
72
+ mutations << Apache::Hadoop::Hbase::Thrift::Mutation.new(:column => column_name).tap do |mutation|
73
+ if new_value = cell.value_to_thrift
74
+ mutation.value = new_value
75
+ else
76
+ mutation.isDelete = true
77
+ end
78
+ end
77
79
  end
78
80
 
79
81
  @table.client.mutateRow(@table.name, id.to_s.dup.force_encoding(Encoding::BINARY), mutations).nil?
80
82
  end
81
83
 
82
84
 
83
- #
84
- # FIXME
85
- #
86
- # The thrift wrapper is only working with strings as far as I can see,
87
- # and the atomicIncrement call on strings kinda doesn't make sense on strings
88
- #
89
- # For now I'll implement this without atomicIncrement, to get the behaviour we want.
90
- # Guess this in time will either be fixed or raised an not-supported-error. If the
91
- # latter is the case I guess we'll need to shift over to a jruby adapter and use the
92
- # java api instead of thrift.
93
- #
85
+
94
86
  def atomic_increment(column_name, by = 1)
95
- # @table.client.atomicIncrement(@table.name, id.to_s, column_name, by)
96
- value_to_increment = @columns[column_name.to_s].value
97
-
98
- raise "Value to increment (#{value_to_increment}) doesnt seem to be a number!" unless value_to_increment =~ /^\d+$/
99
- raise "Argument by must be an integer" unless by.is_a? Fixnum
100
-
101
- value_to_increment = value_to_increment.to_i
102
- value_to_increment += by
103
- value_to_increment = value_to_increment.to_s
104
-
105
- mutation = Apache::Hadoop::Hbase::Thrift::Mutation.new
106
- mutation.column = column_name
107
- mutation.value = value_to_increment
108
-
109
- if @table.client.mutateRow(@table.name, id.to_s, [mutation]).nil?
110
- value_to_increment
111
- end
87
+ @table.client.atomicIncrement(@table.name, id.to_s, column_name, by)
88
+ end
89
+
90
+ def read_atomic_integer_value(column_name)
91
+ atomic_increment(column_name, 0)
112
92
  end
113
93
 
114
94
  def self.populate_from_trow_result(result, connection, table_name, column_families = [])
@@ -42,6 +42,7 @@ module MassiveRecord
42
42
 
43
43
  def destroy
44
44
  disable
45
+ @table_exists = false
45
46
  client.deleteTable(name).nil?
46
47
  end
47
48
 
@@ -114,22 +115,35 @@ module MassiveRecord
114
115
  # table.get("my_id", :info, :name) # => "Bob"
115
116
  #
116
117
  def get(id, column_family_name, column_name)
117
- MassiveRecord::Wrapper::Cell.new(:value => connection.get(name, id, "#{column_family_name.to_s}:#{column_name.to_s}").first.value).value
118
+ if value = connection.get(name, id, "#{column_family_name.to_s}:#{column_name.to_s}").first.try(:value)
119
+ MassiveRecord::Wrapper::Cell.new(:value => value).value # might seems a bit strange.. Just to "enforice" that the value is a supported type
120
+ end
118
121
  end
119
122
 
123
+ #
124
+ # Finds one or multiple ids
125
+ #
126
+ # Returns nil if id is not found
127
+ #
120
128
  def find(*args)
121
- arg = args[0]
122
- opts = args[1] || {}
123
- if arg.is_a?(Array)
124
- arg.collect{|id| first(opts.merge(:start => id))}
129
+ what_to_find = args.first
130
+ options = args.extract_options!.symbolize_keys
131
+
132
+ if what_to_find.is_a?(Array)
133
+ what_to_find.collect { |id| find(id, options) }
125
134
  else
126
- # need to replace by connection.getRowWithColumns("companies_development", "NO0000000812676342", ["info:name", "info:org_num"]).first
127
- first(opts.merge(:start => arg))
135
+ if column_families_to_find = options[:select]
136
+ column_families_to_find = column_families_to_find.collect { |c| c.to_s }
137
+ end
138
+
139
+ if t_row_result = connection.getRowWithColumns(name, what_to_find, column_families_to_find).first
140
+ Row.populate_from_trow_result(t_row_result, connection, name, column_families_to_find)
141
+ end
128
142
  end
129
143
  end
130
-
144
+
131
145
  def find_in_batches(opts = {})
132
- results_limit = opts.delete(:limit)
146
+ results_limit = opts[:limit]
133
147
  results_found = 0
134
148
 
135
149
  scanner(opts) do |s|
@@ -148,7 +162,7 @@ module MassiveRecord
148
162
  end
149
163
 
150
164
  def exists?
151
- connection.tables.include?(name)
165
+ @table_exists ||= connection.tables.include?(name)
152
166
  end
153
167
 
154
168
  def regions
@@ -1,12 +1,31 @@
1
+ unless ActiveModel::AttributeMethods.const_defined? 'COMPILABLE_REGEXP'
2
+ ActiveModel::AttributeMethods::COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\Z/
3
+ end
4
+
1
5
  module MassiveRecord
2
6
  module ORM
3
7
  module AttributeMethods
4
8
  extend ActiveSupport::Concern
5
9
  include ActiveModel::AttributeMethods
10
+ include ActiveModel::MassAssignmentSecurity
6
11
 
7
12
  module ClassMethods
8
13
  def define_attribute_methods
9
14
  super(known_attribute_names)
15
+ @attribute_methods_generated = true
16
+ end
17
+
18
+ def attribute_methods_generated?
19
+ @attribute_methods_generated ||= false
20
+ end
21
+
22
+ def undefine_attribute_methods(*args)
23
+ super
24
+ @attribute_methods_generated = false
25
+ end
26
+
27
+ def attributes_protected_by_default
28
+ ['id', inheritance_attribute]
10
29
  end
11
30
  end
12
31
 
@@ -18,7 +37,7 @@ module MassiveRecord
18
37
  def attributes=(new_attributes)
19
38
  return unless new_attributes.is_a?(Hash)
20
39
 
21
- new_attributes.each do |attr, value|
40
+ sanitize_for_mass_assignment(new_attributes).each do |attr, value|
22
41
  writer_method = "#{attr}="
23
42
  if respond_to? writer_method
24
43
  send(writer_method, value)
@@ -43,6 +62,13 @@ module MassiveRecord
43
62
  super
44
63
  end
45
64
 
65
+
66
+ protected
67
+
68
+ def attribute_method?(attr_name)
69
+ attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
70
+ end
71
+
46
72
  private
47
73
 
48
74
  def attributes_raw=(new_attributes)
@@ -62,11 +62,11 @@ module MassiveRecord
62
62
  end
63
63
 
64
64
  def will_change_attribute?(attr_name, value)
65
- read_attribute(attr_name) != value
65
+ read_attribute(attr_name) != decode_attribute(attr_name, value)
66
66
  end
67
67
 
68
68
  def will_change_back_to_original_value?(attr_name, value)
69
- original_attribute_value(attr_name) == value
69
+ original_attribute_value(attr_name) == decode_attribute(attr_name, value)
70
70
  end
71
71
 
72
72
  def clear_dirty_states!
@@ -8,9 +8,40 @@ module MassiveRecord
8
8
  attribute_method_suffix ""
9
9
  end
10
10
 
11
+
12
+ module ClassMethods
13
+ protected
14
+
15
+ def define_method_attribute(attr_name)
16
+ internal_read_method = "_#{attr_name}"
17
+
18
+ if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
19
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__
20
+ def #{internal_read_method}
21
+ decode_attribute('#{attr_name}', @attributes['#{attr_name}'])
22
+ end
23
+
24
+ alias #{attr_name} #{internal_read_method}
25
+ RUBY
26
+ else
27
+ generated_attribute_methods.send(:define_method, internal_read_method) do
28
+ decode_attribute(attr_name, @attributes[attr_name])
29
+ end
30
+ alias_method "#{attr_name}", "#{internal_read_method}"
31
+ end
32
+ end
33
+ end
34
+
35
+
11
36
  def read_attribute(attr_name)
12
37
  attr_name = attr_name.to_s
13
- attributes_schema[attr_name].nil? ? @attributes[attr_name] : attributes_schema[attr_name].decode(@attributes[attr_name])
38
+ internal_read_method = "_#{attr_name}"
39
+
40
+ if respond_to? internal_read_method
41
+ send(internal_read_method)
42
+ else
43
+ decode_attribute(attr_name, @attributes[attr_name])
44
+ end
14
45
  end
15
46
 
16
47
  private
@@ -18,6 +49,10 @@ module MassiveRecord
18
49
  def attribute(attr_name)
19
50
  read_attribute(attr_name)
20
51
  end
52
+
53
+ def decode_attribute(attr_name, value)
54
+ attributes_schema[attr_name].nil? ? value : attributes_schema[attr_name].decode(value)
55
+ end
21
56
  end
22
57
  end
23
58
  end
@@ -0,0 +1,81 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module AttributeMethods
4
+
5
+ #
6
+ # Module for handling time zones
7
+ #
8
+ # Related attributes and configurations:
9
+ # Base.default_timezone
10
+ # is the zone persisted in the database
11
+ # Base.time_zone_aware_attributes
12
+ # is flag for disable / enable it altogether
13
+ # Base.skip_time_zone_conversion_for_attributes
14
+ # makes it possible to skip specific conversions on given attributes
15
+ #
16
+ #
17
+ module TimeZoneConversion
18
+ extend ActiveSupport::Concern
19
+
20
+ included do
21
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc)
22
+ # when pulling dates and times from the database. This is set to :local by default.
23
+ cattr_accessor :default_timezone, :instance_writer => false
24
+ self.default_timezone = :local
25
+
26
+ cattr_accessor :time_zone_aware_attributes, :instance_writer => false
27
+ self.time_zone_aware_attributes = false
28
+
29
+ class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
30
+ self.skip_time_zone_conversion_for_attributes = []
31
+ end
32
+
33
+
34
+
35
+ module ClassMethods
36
+ protected
37
+
38
+ # Redefine reader method if we are to do time zone configuration on field
39
+ def define_method_attribute(attr_name)
40
+ if time_zone_conversion_on_field?(attributes_schema[attr_name])
41
+ internal_read_method = "_#{attr_name}"
42
+
43
+ if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
44
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__
45
+ def #{internal_read_method}
46
+ if time = decode_attribute('#{attr_name}', @attributes['#{attr_name}'])
47
+ time.in_time_zone
48
+ end
49
+ end
50
+
51
+ alias #{attr_name} #{internal_read_method}
52
+ RUBY
53
+ else
54
+ generated_attribute_methods.send(:define_method, internal_read_method) do
55
+ if time = decode_attribute(attr_name, @attributes[attr_name])
56
+ time.in_time_zone
57
+ end
58
+ end
59
+ end
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ # Redefine writer method if we are to do time zone configuration on field
66
+ def define_method_attribute=(attr_name)
67
+ # Nothing special goes on here, at the moment
68
+ super
69
+ end
70
+
71
+
72
+ private
73
+
74
+ def time_zone_conversion_on_field?(field)
75
+ field && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(field.name) && field.type == :time
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end