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.
- data/CHANGELOG.md +43 -4
- data/Gemfile.lock +3 -1
- data/README.md +5 -0
- data/lib/massive_record/adapters/thrift/connection.rb +23 -16
- data/lib/massive_record/adapters/thrift/row.rb +13 -33
- data/lib/massive_record/adapters/thrift/table.rb +24 -10
- data/lib/massive_record/orm/attribute_methods.rb +27 -1
- data/lib/massive_record/orm/attribute_methods/dirty.rb +2 -2
- data/lib/massive_record/orm/attribute_methods/read.rb +36 -1
- data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +81 -0
- data/lib/massive_record/orm/attribute_methods/write.rb +18 -0
- data/lib/massive_record/orm/base.rb +52 -10
- data/lib/massive_record/orm/callbacks.rb +1 -1
- data/lib/massive_record/orm/default_id.rb +20 -0
- data/lib/massive_record/orm/errors.rb +4 -0
- data/lib/massive_record/orm/finders.rb +102 -57
- data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +45 -0
- data/lib/massive_record/orm/id_factory.rb +1 -1
- data/lib/massive_record/orm/log_subscriber.rb +85 -0
- data/lib/massive_record/orm/persistence.rb +82 -37
- data/lib/massive_record/orm/query_instrumentation.rb +64 -0
- data/lib/massive_record/orm/relations/interface.rb +10 -0
- data/lib/massive_record/orm/relations/metadata.rb +2 -0
- data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
- data/lib/massive_record/orm/schema/field.rb +33 -6
- data/lib/massive_record/orm/timestamps.rb +1 -1
- data/lib/massive_record/orm/validations.rb +2 -2
- data/lib/massive_record/rails/controller_runtime.rb +55 -0
- data/lib/massive_record/rails/railtie.rb +16 -0
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/cell.rb +32 -3
- data/massive_record.gemspec +1 -0
- data/spec/{wrapper/cases → adapter/thrift}/adapter_spec.rb +0 -0
- data/spec/adapter/thrift/atomic_increment_spec.rb +55 -0
- data/spec/{wrapper/cases → adapter/thrift}/connection_spec.rb +0 -10
- data/spec/adapter/thrift/table_find_spec.rb +40 -0
- data/spec/{wrapper/cases → adapter/thrift}/table_spec.rb +55 -13
- data/spec/orm/cases/attribute_methods_spec.rb +6 -1
- data/spec/orm/cases/base_spec.rb +18 -4
- data/spec/orm/cases/callbacks_spec.rb +1 -1
- data/spec/orm/cases/default_id_spec.rb +38 -0
- data/spec/orm/cases/default_values_spec.rb +37 -0
- data/spec/orm/cases/dirty_spec.rb +25 -1
- data/spec/orm/cases/encoding_spec.rb +3 -3
- data/spec/orm/cases/finder_default_scope.rb +8 -1
- data/spec/orm/cases/finder_scope_spec.rb +2 -2
- data/spec/orm/cases/finders_spec.rb +8 -18
- data/spec/orm/cases/id_factory_spec.rb +38 -21
- data/spec/orm/cases/log_subscriber_spec.rb +133 -0
- data/spec/orm/cases/mass_assignment_security_spec.rb +97 -0
- data/spec/orm/cases/persistence_spec.rb +132 -27
- data/spec/orm/cases/single_table_inheritance_spec.rb +2 -2
- data/spec/orm/cases/time_zone_awareness_spec.rb +157 -0
- data/spec/orm/cases/timestamps_spec.rb +15 -0
- data/spec/orm/cases/validation_spec.rb +2 -2
- data/spec/orm/models/model_without_default_id.rb +5 -0
- data/spec/orm/models/person.rb +1 -0
- data/spec/orm/models/test_class.rb +1 -0
- data/spec/orm/relations/interface_spec.rb +2 -2
- data/spec/orm/relations/metadata_spec.rb +1 -1
- data/spec/orm/relations/proxy/references_many_spec.rb +21 -15
- data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +7 -1
- data/spec/orm/relations/proxy/references_one_spec.rb +7 -0
- data/spec/orm/schema/field_spec.rb +61 -5
- data/spec/support/connection_helpers.rb +2 -1
- data/spec/support/mock_massive_record_connection.rb +7 -0
- data/spec/support/time_zone_helper.rb +25 -0
- metadata +51 -14
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,46 @@
|
|
1
|
-
# v0.2.
|
2
|
-
|
3
|
-
|
4
|
-
|
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.
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
massive_record (0.2.
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
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
|
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
|
-
|
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
|