massive_record 0.2.1 → 0.2.2.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +58 -2
- data/Gemfile.lock +17 -17
- data/README.md +98 -41
- data/lib/massive_record.rb +2 -1
- data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
- data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
- data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
- data/lib/massive_record/adapters/thrift/row.rb +35 -4
- data/lib/massive_record/adapters/thrift/table.rb +49 -12
- data/lib/massive_record/orm/attribute_methods.rb +77 -5
- data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
- data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
- data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
- data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
- data/lib/massive_record/orm/base.rb +62 -8
- data/lib/massive_record/orm/column.rb +7 -11
- data/lib/massive_record/orm/default_id.rb +1 -1
- data/lib/massive_record/orm/embedded.rb +66 -0
- data/lib/massive_record/orm/errors.rb +17 -0
- data/lib/massive_record/orm/finders.rb +124 -71
- data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
- data/lib/massive_record/orm/finders/scope.rb +58 -34
- data/lib/massive_record/orm/id_factory.rb +22 -105
- data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
- data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
- data/lib/massive_record/orm/identity_map.rb +256 -0
- data/lib/massive_record/orm/log_subscriber.rb +18 -0
- data/lib/massive_record/orm/observer.rb +69 -0
- data/lib/massive_record/orm/persistence.rb +47 -119
- data/lib/massive_record/orm/persistence/operations.rb +100 -0
- data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
- data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
- data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
- data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
- data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
- data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
- data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
- data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
- data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
- data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
- data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
- data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
- data/lib/massive_record/orm/query_instrumentation.rb +26 -49
- data/lib/massive_record/orm/raw_data.rb +47 -0
- data/lib/massive_record/orm/relations.rb +4 -0
- data/lib/massive_record/orm/relations/interface.rb +134 -0
- data/lib/massive_record/orm/relations/metadata.rb +58 -12
- data/lib/massive_record/orm/relations/proxy.rb +17 -12
- data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
- data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
- data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
- data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
- data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
- data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
- data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
- data/lib/massive_record/orm/schema/column_family.rb +3 -2
- data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
- data/lib/massive_record/orm/schema/field.rb +2 -0
- data/lib/massive_record/orm/schema/table_interface.rb +19 -2
- data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
- data/lib/massive_record/orm/timestamps.rb +17 -7
- data/lib/massive_record/orm/validations.rb +4 -0
- data/lib/massive_record/orm/validations/associated.rb +50 -0
- data/lib/massive_record/rails/railtie.rb +31 -0
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/cell.rb +8 -1
- data/massive_record.gemspec +4 -4
- data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
- data/spec/adapter/thrift/table_find_spec.rb +14 -2
- data/spec/adapter/thrift/table_spec.rb +6 -6
- data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
- data/spec/orm/cases/attribute_methods_spec.rb +215 -22
- data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
- data/spec/orm/cases/change_id_spec.rb +62 -0
- data/spec/orm/cases/default_id_spec.rb +25 -6
- data/spec/orm/cases/default_values_spec.rb +6 -3
- data/spec/orm/cases/dirty_spec.rb +150 -102
- data/spec/orm/cases/embedded_spec.rb +250 -0
- data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
- data/spec/orm/cases/finder_scope_spec.rb +96 -29
- data/spec/orm/cases/finders_spec.rb +57 -10
- data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
- data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
- data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
- data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
- data/spec/orm/cases/log_subscriber_spec.rb +15 -2
- data/spec/orm/cases/observing_spec.rb +61 -0
- data/spec/orm/cases/persistence_spec.rb +151 -60
- data/spec/orm/cases/raw_data_spec.rb +58 -0
- data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
- data/spec/orm/cases/table_spec.rb +3 -3
- data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
- data/spec/orm/cases/timestamps_spec.rb +23 -109
- data/spec/orm/cases/validation_spec.rb +9 -0
- data/spec/orm/models/address.rb +5 -1
- data/spec/orm/models/address_with_timestamp.rb +12 -0
- data/spec/orm/models/car.rb +5 -0
- data/spec/orm/models/person.rb +13 -1
- data/spec/orm/models/person_with_timestamp.rb +4 -2
- data/spec/orm/models/test_class.rb +1 -0
- data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
- data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
- data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
- data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
- data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
- data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
- data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
- data/spec/orm/persistence/operations/insert_spec.rb +31 -0
- data/spec/orm/persistence/operations/reload_spec.rb +48 -0
- data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
- data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
- data/spec/orm/persistence/operations/update_spec.rb +25 -0
- data/spec/orm/persistence/operations_spec.rb +58 -0
- data/spec/orm/relations/interface_spec.rb +188 -0
- data/spec/orm/relations/metadata_spec.rb +92 -15
- data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
- data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
- data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
- data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
- data/spec/orm/schema/column_family_spec.rb +21 -0
- data/spec/orm/schema/embedded_interface_spec.rb +181 -0
- data/spec/orm/schema/field_spec.rb +7 -0
- data/spec/orm/schema/table_interface_spec.rb +31 -1
- data/spec/shared/orm/id_factories.rb +44 -0
- data/spec/shared/orm/model_with_timestamps.rb +132 -0
- data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
- data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
- data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
- data/spec/shared/orm/relations/proxy.rb +9 -2
- data/spec/spec_helper.rb +9 -0
- data/spec/support/mock_massive_record_connection.rb +2 -1
- metadata +106 -21
- data/spec/orm/cases/column_spec.rb +0 -49
- data/spec/orm/cases/id_factory_spec.rb +0 -92
- data/spec/orm/schema/column_interface_spec.rb +0 -136
@@ -2,64 +2,22 @@ require 'singleton'
|
|
2
2
|
|
3
3
|
module MassiveRecord
|
4
4
|
module ORM
|
5
|
+
module IdFactory
|
6
|
+
extend ActiveSupport::Concern
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
# Usage:
|
10
|
-
# IdFactory.next_for(:cars) # => 1
|
11
|
-
# IdFactory.next_for(:cars) # => 2
|
12
|
-
# IdFactory.next_for(AClassRespondingToTableName) # => 1
|
13
|
-
# IdFactory.next_for("a_class_responding_to_table_names") # => 2
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# Storage:
|
17
|
-
# Stored in id_factories table, under column family named tables.
|
18
|
-
# Field name equals to tables it has generated ids for, and it's
|
19
|
-
# values is integers (if the adapter supports it).
|
20
|
-
#
|
21
|
-
class IdFactory < Table
|
22
|
-
include Singleton
|
23
|
-
|
24
|
-
COLUMN_FAMILY_FOR_TABLES = :tables
|
25
|
-
ID = "id_factory"
|
26
|
-
|
27
|
-
column_family COLUMN_FAMILY_FOR_TABLES do
|
28
|
-
autoload_fields
|
8
|
+
included do
|
9
|
+
include Singleton
|
29
10
|
end
|
30
11
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def self.instance
|
38
|
-
if table_exists?
|
39
|
-
begin
|
40
|
-
if @instance
|
41
|
-
@instance.reload # If, for some reason, the record has been removed. Will be rescued and set to nil
|
42
|
-
else
|
43
|
-
@instance = find(ID)
|
44
|
-
end
|
45
|
-
rescue RecordNotFound
|
46
|
-
@instance = nil
|
47
|
-
end
|
12
|
+
module ClassMethods
|
13
|
+
#
|
14
|
+
# Delegates to the instance, just a shout cut.
|
15
|
+
#
|
16
|
+
def next_for(table)
|
17
|
+
instance.next_for(table)
|
48
18
|
end
|
49
|
-
|
50
|
-
@instance = new unless @instance
|
51
|
-
@instance
|
52
19
|
end
|
53
20
|
|
54
|
-
#
|
55
|
-
# Delegates to the instance, just a shout cut.
|
56
|
-
#
|
57
|
-
def self.next_for(table)
|
58
|
-
instance.next_for(table)
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
21
|
#
|
64
22
|
# Returns a new and unique id for a given table name
|
65
23
|
# Table can a symbol, string or an object responding to table_name
|
@@ -70,64 +28,23 @@ module MassiveRecord
|
|
70
28
|
end
|
71
29
|
|
72
30
|
|
73
|
-
|
74
|
-
def id
|
75
|
-
ID
|
76
|
-
end
|
77
|
-
|
78
31
|
private
|
79
32
|
|
80
33
|
#
|
81
|
-
#
|
82
|
-
#
|
34
|
+
# Methods which generates next id, will receive at least
|
35
|
+
# :table => 'name' as options
|
83
36
|
#
|
84
|
-
def next_id(options = {})
|
85
|
-
|
86
|
-
table_name = options.delete :table
|
87
|
-
|
88
|
-
create_field_or_ensure_type_integer_for(table_name)
|
89
|
-
atomic_increment!(table_name)
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
def create_field_or_ensure_type_integer_for(table_name)
|
96
|
-
if has_field_for? table_name
|
97
|
-
ensure_type_integer_for(table_name)
|
98
|
-
else
|
99
|
-
create_field_for(table_name)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
# Creates a field for a table name which is new
|
106
|
-
# Feels a bit hackish, hooking in and doing some of what the
|
107
|
-
# autoload-functionality of column_family block above does too.
|
108
|
-
# But at least, we can "dynamicly" assign new attributes to this object.
|
109
|
-
#
|
110
|
-
def create_field_for(table_name)
|
111
|
-
add_field_to_column_family COLUMN_FAMILY_FOR_TABLES, table_name, :integer, :default => 0
|
112
|
-
end
|
113
|
-
|
114
|
-
#
|
115
|
-
# Just makes sure that definition of a field is set to integer.
|
116
|
-
# This is needed as the autoload functionlaity sets all types to strings.
|
117
|
-
#
|
118
|
-
def ensure_type_integer_for(table_name)
|
119
|
-
column_family_for_tables.field_by_name(table_name).type = :integer
|
120
|
-
self[table_name] = 0 if self[table_name].blank?
|
121
|
-
end
|
122
|
-
|
123
|
-
|
124
|
-
def has_field_for?(table_name)
|
125
|
-
respond_to? table_name
|
126
|
-
end
|
127
|
-
|
128
|
-
def column_family_for_tables
|
129
|
-
@column_family_for_tables ||= column_families.family_by_name(COLUMN_FAMILY_FOR_TABLES)
|
37
|
+
def next_id(options = {})
|
38
|
+
raise "Needs implementation :-)"
|
130
39
|
end
|
131
40
|
end
|
132
41
|
end
|
133
42
|
end
|
43
|
+
|
44
|
+
require 'massive_record/orm/id_factory/atomic_incrementation'
|
45
|
+
require 'massive_record/orm/id_factory/timestamp'
|
46
|
+
|
47
|
+
ActiveSupport.on_load(:massive_record) do
|
48
|
+
MassiveRecord::ORM::Base.id_factory = MassiveRecord::ORM::IdFactory::AtomicIncrementation
|
49
|
+
MassiveRecord::ORM::Embedded.id_factory = MassiveRecord::ORM::IdFactory::Timestamp
|
50
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module IdFactory
|
4
|
+
|
5
|
+
#
|
6
|
+
# A factory class for unique IDs for any given tables.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# AtomicIncrementation.next_for(:cars) # => 1
|
10
|
+
# AtomicIncrementation.next_for(:cars) # => 2
|
11
|
+
# AtomicIncrementation.next_for(AClassRespondingToTableName) # => 1
|
12
|
+
# AtomicIncrementation.next_for("a_class_responding_to_table_names") # => 2
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# Storage:
|
16
|
+
# Stored in id_factories table, under column family named tables.
|
17
|
+
# Field name equals to tables it has generated ids for, and it's
|
18
|
+
# values is integers (if the adapter supports it).
|
19
|
+
#
|
20
|
+
class AtomicIncrementation < Table
|
21
|
+
include IdFactory
|
22
|
+
|
23
|
+
COLUMN_FAMILY_FOR_TABLES = :tables
|
24
|
+
ID = "id_factory"
|
25
|
+
|
26
|
+
set_table_name "id_factories"
|
27
|
+
|
28
|
+
column_family COLUMN_FAMILY_FOR_TABLES do
|
29
|
+
autoload_fields :type => :integer
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Returns the factory, singleton class.
|
34
|
+
# It will be a reloaded version each time instance
|
35
|
+
# is retrieved, or else it will fetch self from the
|
36
|
+
# database, or if all other fails return a new of self.
|
37
|
+
#
|
38
|
+
def self.instance
|
39
|
+
if table_exists?
|
40
|
+
begin
|
41
|
+
if @instance
|
42
|
+
@instance.reload # If, for some reason, the record has been removed. Will be rescued and set to nil
|
43
|
+
else
|
44
|
+
@instance = find(ID)
|
45
|
+
end
|
46
|
+
rescue RecordNotFound
|
47
|
+
@instance = nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@instance = new unless @instance
|
52
|
+
@instance
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
def id
|
58
|
+
ID
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
#
|
64
|
+
# Method which actually does the increment work for
|
65
|
+
# a given table name as string
|
66
|
+
#
|
67
|
+
def next_id(options = {})
|
68
|
+
options.assert_valid_keys(:table)
|
69
|
+
table_name = options.delete :table
|
70
|
+
|
71
|
+
create_field_or_ensure_type_integer_for(table_name)
|
72
|
+
atomic_increment!(table_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
def create_field_or_ensure_type_integer_for(table_name)
|
79
|
+
if has_field_for? table_name
|
80
|
+
ensure_type_integer_for(table_name)
|
81
|
+
else
|
82
|
+
create_field_for(table_name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# Creates a field for a table name which is new
|
89
|
+
# Feels a bit hackish, hooking in and doing some of what the
|
90
|
+
# autoload-functionality of column_family block above does too.
|
91
|
+
# But at least, we can "dynamicly" assign new attributes to this object.
|
92
|
+
#
|
93
|
+
def create_field_for(table_name)
|
94
|
+
add_field_to_column_family COLUMN_FAMILY_FOR_TABLES, table_name, :integer, :default => 0
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Just makes sure that definition of a field is set to integer.
|
99
|
+
# This is needed as the autoload functionlaity sets all types to strings.
|
100
|
+
#
|
101
|
+
def ensure_type_integer_for(table_name)
|
102
|
+
column_family_for_tables.field_by_name(table_name).type = :integer
|
103
|
+
self[table_name] = 0 if self[table_name].blank?
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def has_field_for?(table_name)
|
108
|
+
respond_to? table_name
|
109
|
+
end
|
110
|
+
|
111
|
+
def column_family_for_tables
|
112
|
+
@column_family_for_tables ||= column_families.family_by_name(COLUMN_FAMILY_FOR_TABLES)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module IdFactory
|
4
|
+
|
5
|
+
#
|
6
|
+
# Factory class for ids based on time stamps. It does not guarantee uniqueness
|
7
|
+
# on ids, but if you use microseconds as precision you should be at least a bit
|
8
|
+
# better of then using seconds..
|
9
|
+
#
|
10
|
+
# This factory is mostly intended to generate ids for embedded records, and was
|
11
|
+
# written as a result of Companybook not wanting to id the AtomicIncrementation
|
12
|
+
# id factory for every embedded record. It might be, in the future, an idea to
|
13
|
+
# take a second look at this one to make it guarantee it's uniqueness of ids.
|
14
|
+
#
|
15
|
+
class Timestamp
|
16
|
+
include IdFactory
|
17
|
+
|
18
|
+
cattr_accessor :precision, :reverse_time, :instance_writer => false
|
19
|
+
self.precision = :microseconds
|
20
|
+
self.reverse_time = true
|
21
|
+
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def next_id(options = {})
|
26
|
+
options.assert_valid_keys(:table)
|
27
|
+
table_name = options.delete :table
|
28
|
+
|
29
|
+
time_to_id Time.now
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def time_to_id(time)
|
34
|
+
floated_time = time.getutc.to_f
|
35
|
+
|
36
|
+
case precision
|
37
|
+
when :s, :seconds
|
38
|
+
if reverse_time
|
39
|
+
(10**10 - 1 - (floated_time).to_i).to_s
|
40
|
+
else
|
41
|
+
(floated_time).to_i.to_s
|
42
|
+
end
|
43
|
+
when :ms, :milliseconds
|
44
|
+
if reverse_time
|
45
|
+
(10**13 - 1 - (floated_time * 1000).to_i).to_s
|
46
|
+
else
|
47
|
+
(floated_time * 1000).to_i.to_s
|
48
|
+
end
|
49
|
+
when :us, :microseconds
|
50
|
+
if reverse_time
|
51
|
+
(10**16 - 1 - (floated_time * 1000000).to_i).to_s
|
52
|
+
else
|
53
|
+
(floated_time * 1000000).to_i.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
|
4
|
+
#
|
5
|
+
# The goal of the IdentiyMap is to make sure that the same object is not loaded twice
|
6
|
+
# from the database, but uses the same object if you do 2.times { AClass.find(1) }.
|
7
|
+
#
|
8
|
+
# To get a quick introduction on IdentityMap see: http://www.martinfowler.com/eaaCatalog/identityMap.html
|
9
|
+
#
|
10
|
+
# You can enable / disable Identity map by doing:
|
11
|
+
# MassiveRecord::ORM::IdentityMap.enabled = flag
|
12
|
+
#
|
13
|
+
module IdentityMap
|
14
|
+
extend ActiveSupport::Concern
|
15
|
+
|
16
|
+
#
|
17
|
+
# Error is raised internally of the identity map to signal that
|
18
|
+
# you tried to get a record from a parent class, but you looked
|
19
|
+
# it up via it's sub class. For instance A is a super class of B.
|
20
|
+
# IdentityMap.get(B, "id-belonging-to-an-A-class") will raise the
|
21
|
+
# error.
|
22
|
+
#
|
23
|
+
# This error will however not "leak" outside of the identity map's
|
24
|
+
# code. It should be handled internally, and the goal for it is just
|
25
|
+
# not to hit the database more than we need to if we know that the
|
26
|
+
# database will return nil as well.
|
27
|
+
#
|
28
|
+
class RecordIsSuperClassOfQueriedClass < MassiveRecordError; end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
#
|
32
|
+
# Switch to either turn on or off the identity map
|
33
|
+
#
|
34
|
+
def enabled=(boolean)
|
35
|
+
Thread.current[:identity_map_enabled] = boolean
|
36
|
+
end
|
37
|
+
|
38
|
+
def enabled
|
39
|
+
!!Thread.current[:identity_map_enabled]
|
40
|
+
end
|
41
|
+
alias enabled? enabled
|
42
|
+
|
43
|
+
|
44
|
+
#
|
45
|
+
# Call this with a block to ensure that IdentityMap is enabled
|
46
|
+
# for that block and reset to it's origianl setting thereafter
|
47
|
+
#
|
48
|
+
def use
|
49
|
+
original_value, self.enabled = enabled, true
|
50
|
+
yield
|
51
|
+
ensure
|
52
|
+
self.enabled = original_value
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Call this with a block to ensure that IdentityMap is disabled
|
57
|
+
# for that block and reset to it's origianl setting thereafter
|
58
|
+
#
|
59
|
+
def without
|
60
|
+
original_value, self.enabled = enabled, false
|
61
|
+
yield
|
62
|
+
ensure
|
63
|
+
self.enabled = original_value
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
def get(klass, *ids)
|
69
|
+
get_many = ids.first.is_a?(Array)
|
70
|
+
|
71
|
+
ids.flatten!
|
72
|
+
|
73
|
+
result = case ids.length
|
74
|
+
when 0
|
75
|
+
raise ArgumentError.new("Must have at least one ID!")
|
76
|
+
when 1
|
77
|
+
result = get_one(klass, ids.first)
|
78
|
+
get_many ? [result].compact : result
|
79
|
+
else
|
80
|
+
get_some(klass, ids)
|
81
|
+
end
|
82
|
+
|
83
|
+
if records = Array(result).compact and records.any?
|
84
|
+
ActiveSupport::Notifications.instrument("identity_map.massive_record", {
|
85
|
+
:name => [klass, 'loaded from identity map'].join(' '),
|
86
|
+
:records => records
|
87
|
+
}) do
|
88
|
+
result
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
def add(record)
|
96
|
+
return if record.nil?
|
97
|
+
|
98
|
+
repository[record_class_to_repository_key(record)][record.id] = record
|
99
|
+
end
|
100
|
+
|
101
|
+
def remove(record)
|
102
|
+
remove_by_id record.class, record.id
|
103
|
+
end
|
104
|
+
|
105
|
+
def remove_by_id(klass, id)
|
106
|
+
repository[class_to_repository_key(klass)].delete id
|
107
|
+
end
|
108
|
+
|
109
|
+
delegate :clear, :to => :repository
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def get_one(klass, id)
|
116
|
+
if record = repository[class_to_repository_key(klass)][id]
|
117
|
+
if klass == record.class || klass.descendants.include?(record.class)
|
118
|
+
record
|
119
|
+
else
|
120
|
+
raise RecordIsSuperClassOfQueriedClass.new("#{record.class} is a super class of #{klass}. Please look your #{klass}-record up by do a #{klass}.find(#{record.id.inspect})")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_some(klass, ids)
|
126
|
+
ids.collect do |id|
|
127
|
+
begin
|
128
|
+
get_one(klass, id)
|
129
|
+
rescue RecordIsSuperClassOfQueriedClass
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end.compact
|
133
|
+
end
|
134
|
+
|
135
|
+
def repository
|
136
|
+
Thread.current[:identity_map_repository] ||= Hash.new { |hash, key| hash[key] = {} }
|
137
|
+
end
|
138
|
+
|
139
|
+
def record_class_to_repository_key(record)
|
140
|
+
class_to_repository_key record.class
|
141
|
+
end
|
142
|
+
|
143
|
+
def class_to_repository_key(klass)
|
144
|
+
klass.base_class
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
module ClassMethods
|
153
|
+
private
|
154
|
+
|
155
|
+
|
156
|
+
def find_one(id, options)
|
157
|
+
return super unless IdentityMap.enabled? && can_use_identity_map_with?(options)
|
158
|
+
|
159
|
+
IdentityMap.get(self, id) || IdentityMap.add(super)
|
160
|
+
rescue RecordIsSuperClassOfQueriedClass
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
164
|
+
def find_some(ids, options)
|
165
|
+
return super unless IdentityMap.enabled? && can_use_identity_map_with?(options)
|
166
|
+
|
167
|
+
records_from_database = []
|
168
|
+
records_from_identity_map = IdentityMap.get(self, ids)
|
169
|
+
|
170
|
+
missing_ids = ids - records_from_identity_map.collect(&:id)
|
171
|
+
|
172
|
+
if missing_ids.any?
|
173
|
+
records_from_database = super(missing_ids, options)
|
174
|
+
records_from_database.each { |record| IdentityMap.add(record) }
|
175
|
+
end
|
176
|
+
|
177
|
+
records_from_identity_map | records_from_database
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
def can_use_identity_map_with?(finder_options)
|
183
|
+
!finder_options.has_key?(:select)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
module InstanceMethods
|
190
|
+
def reload
|
191
|
+
IdentityMap.remove(self) if IdentityMap.enabled?
|
192
|
+
super
|
193
|
+
end
|
194
|
+
|
195
|
+
def destroy
|
196
|
+
return super unless IdentityMap.enabled?
|
197
|
+
|
198
|
+
super.tap { IdentityMap.remove(self) }
|
199
|
+
end
|
200
|
+
alias_method :delete, :destroy
|
201
|
+
|
202
|
+
def change_id!(new_id)
|
203
|
+
IdentityMap.remove(self)
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
|
210
|
+
def create
|
211
|
+
return super unless IdentityMap.enabled?
|
212
|
+
|
213
|
+
super.tap { IdentityMap.add(self) }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
|
219
|
+
|
220
|
+
class Middleware
|
221
|
+
class BodyProxy
|
222
|
+
def initialize(target, original_identity_map_state)
|
223
|
+
@target = target
|
224
|
+
@original_identity_map_state = original_identity_map_state
|
225
|
+
end
|
226
|
+
|
227
|
+
def each(&block)
|
228
|
+
@target.each(&block)
|
229
|
+
end
|
230
|
+
|
231
|
+
def close
|
232
|
+
@target.close if @target.respond_to?(:close)
|
233
|
+
ensure
|
234
|
+
IdentityMap.enabled = @original_identity_map_state
|
235
|
+
IdentityMap.clear
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
def initialize(app)
|
243
|
+
@app = app
|
244
|
+
end
|
245
|
+
|
246
|
+
def call(env)
|
247
|
+
original_identity_map_state = IdentityMap.enabled?
|
248
|
+
IdentityMap.enabled = true
|
249
|
+
|
250
|
+
status, headers, body = @app.call(env)
|
251
|
+
[status, headers, BodyProxy.new(body, original_identity_map_state)]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|