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.
Files changed (135) hide show
  1. data/CHANGELOG.md +58 -2
  2. data/Gemfile.lock +17 -17
  3. data/README.md +98 -41
  4. data/lib/massive_record.rb +2 -1
  5. data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
  6. data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
  7. data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
  8. data/lib/massive_record/adapters/thrift/row.rb +35 -4
  9. data/lib/massive_record/adapters/thrift/table.rb +49 -12
  10. data/lib/massive_record/orm/attribute_methods.rb +77 -5
  11. data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
  12. data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
  13. data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
  14. data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
  15. data/lib/massive_record/orm/base.rb +62 -8
  16. data/lib/massive_record/orm/column.rb +7 -11
  17. data/lib/massive_record/orm/default_id.rb +1 -1
  18. data/lib/massive_record/orm/embedded.rb +66 -0
  19. data/lib/massive_record/orm/errors.rb +17 -0
  20. data/lib/massive_record/orm/finders.rb +124 -71
  21. data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
  22. data/lib/massive_record/orm/finders/scope.rb +58 -34
  23. data/lib/massive_record/orm/id_factory.rb +22 -105
  24. data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
  25. data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
  26. data/lib/massive_record/orm/identity_map.rb +256 -0
  27. data/lib/massive_record/orm/log_subscriber.rb +18 -0
  28. data/lib/massive_record/orm/observer.rb +69 -0
  29. data/lib/massive_record/orm/persistence.rb +47 -119
  30. data/lib/massive_record/orm/persistence/operations.rb +100 -0
  31. data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
  32. data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
  33. data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
  34. data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
  35. data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
  36. data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
  37. data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
  38. data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
  39. data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
  40. data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
  41. data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
  42. data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
  43. data/lib/massive_record/orm/query_instrumentation.rb +26 -49
  44. data/lib/massive_record/orm/raw_data.rb +47 -0
  45. data/lib/massive_record/orm/relations.rb +4 -0
  46. data/lib/massive_record/orm/relations/interface.rb +134 -0
  47. data/lib/massive_record/orm/relations/metadata.rb +58 -12
  48. data/lib/massive_record/orm/relations/proxy.rb +17 -12
  49. data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
  50. data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
  51. data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
  52. data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
  53. data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
  54. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
  55. data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
  56. data/lib/massive_record/orm/schema/column_family.rb +3 -2
  57. data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
  58. data/lib/massive_record/orm/schema/field.rb +2 -0
  59. data/lib/massive_record/orm/schema/table_interface.rb +19 -2
  60. data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
  61. data/lib/massive_record/orm/timestamps.rb +17 -7
  62. data/lib/massive_record/orm/validations.rb +4 -0
  63. data/lib/massive_record/orm/validations/associated.rb +50 -0
  64. data/lib/massive_record/rails/railtie.rb +31 -0
  65. data/lib/massive_record/version.rb +1 -1
  66. data/lib/massive_record/wrapper/cell.rb +8 -1
  67. data/massive_record.gemspec +4 -4
  68. data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
  69. data/spec/adapter/thrift/table_find_spec.rb +14 -2
  70. data/spec/adapter/thrift/table_spec.rb +6 -6
  71. data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
  72. data/spec/orm/cases/attribute_methods_spec.rb +215 -22
  73. data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
  74. data/spec/orm/cases/change_id_spec.rb +62 -0
  75. data/spec/orm/cases/default_id_spec.rb +25 -6
  76. data/spec/orm/cases/default_values_spec.rb +6 -3
  77. data/spec/orm/cases/dirty_spec.rb +150 -102
  78. data/spec/orm/cases/embedded_spec.rb +250 -0
  79. data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
  80. data/spec/orm/cases/finder_scope_spec.rb +96 -29
  81. data/spec/orm/cases/finders_spec.rb +57 -10
  82. data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
  83. data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
  84. data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
  85. data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
  86. data/spec/orm/cases/log_subscriber_spec.rb +15 -2
  87. data/spec/orm/cases/observing_spec.rb +61 -0
  88. data/spec/orm/cases/persistence_spec.rb +151 -60
  89. data/spec/orm/cases/raw_data_spec.rb +58 -0
  90. data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
  91. data/spec/orm/cases/table_spec.rb +3 -3
  92. data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
  93. data/spec/orm/cases/timestamps_spec.rb +23 -109
  94. data/spec/orm/cases/validation_spec.rb +9 -0
  95. data/spec/orm/models/address.rb +5 -1
  96. data/spec/orm/models/address_with_timestamp.rb +12 -0
  97. data/spec/orm/models/car.rb +5 -0
  98. data/spec/orm/models/person.rb +13 -1
  99. data/spec/orm/models/person_with_timestamp.rb +4 -2
  100. data/spec/orm/models/test_class.rb +1 -0
  101. data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
  102. data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
  103. data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
  104. data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
  105. data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
  106. data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
  107. data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
  108. data/spec/orm/persistence/operations/insert_spec.rb +31 -0
  109. data/spec/orm/persistence/operations/reload_spec.rb +48 -0
  110. data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
  111. data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
  112. data/spec/orm/persistence/operations/update_spec.rb +25 -0
  113. data/spec/orm/persistence/operations_spec.rb +58 -0
  114. data/spec/orm/relations/interface_spec.rb +188 -0
  115. data/spec/orm/relations/metadata_spec.rb +92 -15
  116. data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
  117. data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
  118. data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
  119. data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
  120. data/spec/orm/schema/column_family_spec.rb +21 -0
  121. data/spec/orm/schema/embedded_interface_spec.rb +181 -0
  122. data/spec/orm/schema/field_spec.rb +7 -0
  123. data/spec/orm/schema/table_interface_spec.rb +31 -1
  124. data/spec/shared/orm/id_factories.rb +44 -0
  125. data/spec/shared/orm/model_with_timestamps.rb +132 -0
  126. data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
  127. data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
  128. data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
  129. data/spec/shared/orm/relations/proxy.rb +9 -2
  130. data/spec/spec_helper.rb +9 -0
  131. data/spec/support/mock_massive_record_connection.rb +2 -1
  132. metadata +106 -21
  133. data/spec/orm/cases/column_spec.rb +0 -49
  134. data/spec/orm/cases/id_factory_spec.rb +0 -92
  135. 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
- # A factory class for unique IDs for any given tables.
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
- # Returns the factory, singleton class.
33
- # It will be a reloaded version each time instance
34
- # is retrieved, or else it will fetch self from the
35
- # database, or if all other fails return a new of self.
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
- # Method which actually does the increment work for
82
- # a given table name as string
34
+ # Methods which generates next id, will receive at least
35
+ # :table => 'name' as options
83
36
  #
84
- def next_id(options = {})
85
- options.assert_valid_keys(:table)
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