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