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