massive_record 0.2.0 → 0.2.1.rc1

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