massive_record 0.2.1 → 0.2.2.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 +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
|