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
@@ -1,18 +1,14 @@
1
- require 'massive_record/orm/schema/column_interface'
2
-
3
1
  module MassiveRecord
4
2
  module ORM
5
3
  class Column < Base
6
- include Schema::ColumnInterface
7
-
8
- # TODO Column does not support these kind of methods
9
- class << self
10
- undef_method :first, :last, :all, :exists?, :destroy_all
4
+ def self.inherited(by_class)
5
+ raise(<<-TXT
6
+ #{by_class} inherits from MassiveRecord::ORM::Column which has been renamed to
7
+ MassiveRecord::ORM::Embedded. Please inherit from the Embedded class instead as
8
+ Column will be removed in the an upcomming of MassiveRecord.
9
+ TXT
10
+ )
11
11
  end
12
-
13
- undef_method :create, :reload, :save, :save!, :update_attribute, :update_attributes,
14
- :update_attributes!, :touch, :destroy, :increment!, :atomic_increment!,
15
- :decrement!, :delete
16
12
  end
17
13
  end
18
14
  end
@@ -4,7 +4,7 @@ module MassiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- before_create :ensure_record_has_id, :if => :auto_increment_id
7
+ before_create :ensure_record_has_id, :if => :set_id_from_factory_before_create
8
8
  end
9
9
 
10
10
 
@@ -0,0 +1,66 @@
1
+ require 'massive_record/orm/schema/embedded_interface'
2
+
3
+ module MassiveRecord
4
+ module ORM
5
+ class Embedded < Base
6
+ include Schema::EmbeddedInterface
7
+
8
+ DATABASE_ID_SEPARATOR = '|'
9
+
10
+
11
+ # TODO Embedded does not support these kind of methods
12
+ class << self
13
+ undef_method :first, :last, :all, :exists?, :destroy_all
14
+ end
15
+
16
+
17
+ def self.parse_database_id(database_id)
18
+ if splitted = database_id.split(DATABASE_ID_SEPARATOR) and splitted.length == 2
19
+ splitted
20
+ else
21
+ fail InvalidEmbeddedDatabaseId.new(
22
+ <<-TXT
23
+ Expected database id '#{database_id}' to be on a format like
24
+ base_class_here#{DATABASE_ID_SEPARATOR}record_id_here
25
+ TXT
26
+ )
27
+ end
28
+ end
29
+
30
+ def self.database_id(klass, id)
31
+ [klass.base_class.to_s.underscore, id].join(DATABASE_ID_SEPARATOR)
32
+ end
33
+
34
+ #
35
+ # Database id is base_class plus the record's id.
36
+ # It is given, as we might want to embed records in an existing
37
+ # column family, or share a family for multiple types. In which case,
38
+ # we'll end up with a column family like this:
39
+ #
40
+ # | key | attributes |
41
+ # --------------------------------------------------------------------------
42
+ # | "address|123" | { :street => "Askerveien", :number => "12", etc... } |
43
+ # | "address|124" | { :street => "Askerveien", :number => "12", etc... } |
44
+ # | "name" | "Thorbjorn Hermansen" |
45
+ # | "age" | "30" |
46
+ #
47
+ # ..in this case we fetch embedded records to collection addresses by scoping
48
+ # on keys which starts with base_class name. The records itself will only have
49
+ # id equal to 123 and 124.
50
+ #
51
+ def database_id # :nodoc:
52
+ if id
53
+ self.class.database_id(self.class, id)
54
+ end
55
+ end
56
+
57
+ #
58
+ # Writer for database id. Used when loading records to easily set record's id.
59
+ # Should not have the need to be used in other situations.
60
+ #
61
+ def database_id=(database_id) # :nodoc:
62
+ self.id = self.class.parse_database_id(database_id)[1]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -66,8 +66,25 @@ module MassiveRecord
66
66
  class RelationTypeMismatch < MassiveRecordError
67
67
  end
68
68
 
69
+ # Used if we are dependent on a relation, but it does not seem to exist. For instance,
70
+ # embedded_in is dependent of it's inverse in the owner object.
71
+ class RelationMissing < MassiveRecordError
72
+ end
73
+
69
74
  # Raised when an attribute is decoded from the database, but the type returned does not match what is expected
70
75
  class SerializationTypeMismatch < MassiveRecordError
71
76
  end
77
+
78
+ # Raised when an ORM::Embedded is asked to save itself without being assigned a collection to be embedded in
79
+ class NotAssignedToEmbeddedCollection < MassiveRecordError
80
+ attr_reader :embedded_in_missing_values
81
+ def initialize(record, embedded_in_missing_values)
82
+ @embedded_in_missing_values = embedded_in_missing_values
83
+ super("#{record.inspect} needs to be embedded in collection before save. Embedded-in-attribute(s) missing assignment(s): #{embedded_in_missing_values.join(', ')}")
84
+ end
85
+ end
86
+
87
+ class InvalidEmbeddedDatabaseId < MassiveRecordError
88
+ end
72
89
  end
73
90
  end
@@ -5,13 +5,17 @@ module MassiveRecord
5
5
 
6
6
  included do
7
7
  class << self
8
- delegate :find, :first, :last, :all, :select, :limit, :to => :finder_scope
8
+ delegate :find, :last, :all, :select, :limit, :starts_with, :offset, :to => :finder_scope
9
9
  end
10
10
 
11
11
  class_attribute :default_scoping, :instance_writer => false
12
12
  end
13
13
 
14
14
  module ClassMethods
15
+ def first(*args)
16
+ finder_scope.first(*args)
17
+ end
18
+
15
19
  #
16
20
  # Find records in batches. Makes it easier to work with
17
21
  # big data sets where you don't want to load every record up front.
@@ -19,7 +23,7 @@ module MassiveRecord
19
23
  def find_in_batches(*args)
20
24
  table.find_in_batches(*args) do |rows|
21
25
  records = rows.collect do |row|
22
- instantiate(transpose_hbase_columns_to_record_attributes(row))
26
+ instantiate(*transpose_hbase_row_to_record_attributes_and_raw_data(row))
23
27
  end
24
28
  yield records
25
29
  end
@@ -51,7 +55,11 @@ module MassiveRecord
51
55
  # Entry point for method delegation like find, first, all etc.
52
56
  #
53
57
  def finder_scope
54
- default_scoping || unscoped
58
+ if default_scoping
59
+ default_scoping.dup
60
+ else
61
+ unscoped
62
+ end
55
63
  end
56
64
 
57
65
 
@@ -65,7 +73,7 @@ module MassiveRecord
65
73
  when Scope, nil
66
74
  scope
67
75
  when Hash
68
- Scope.new(self, :find_options => scope)
76
+ Scope.new(self).apply_finder_options(scope)
69
77
  else
70
78
  raise "Don't know how to set scope with #{scope.class}."
71
79
  end
@@ -83,107 +91,152 @@ module MassiveRecord
83
91
 
84
92
 
85
93
  #
86
- # This do_find method is not very nice it's logic should be re-factored at some point.
94
+ # Method actually doing the find operation. It handles first, last (not supported though), all
95
+ # and find records by id(s). It simply delegates to more spesific methods.
87
96
  #
88
97
  def do_find(*args) # :nodoc:
89
98
  options = args.extract_options!.to_options
90
- raise ArgumentError.new("At least one argument required!") if args.empty?
91
- raise RecordNotFound.new("Can't find a #{model_name.human} without an ID.") if args.first.nil?
99
+
92
100
  raise ArgumentError.new("Sorry, conditions are not supported!") if options.has_key? :conditions
93
101
 
94
- skip_expected_result_check = options.delete(:skip_expected_result_check)
102
+ case args.first
103
+ when :first, :last
104
+ send(args.first, options)
105
+ when :all
106
+ find_all(options)
107
+ else
108
+ find_by_ids(*args, options)
109
+ end
110
+ end
95
111
 
96
- args << options
97
112
 
98
- type = args.shift if args.first.is_a? Symbol
99
- find_many = type == :all
100
- expected_result_size = nil
101
- what_to_find = []
102
- result_from_table = []
103
-
104
- find_many, expected_result_size, what_to_find, result_from_table = query_hbase(type, args, find_many)
105
-
106
- # Filter out unexpected IDs (unless type is set (all/first), in that case
107
- # we have no expectations on the returned rows' ids)
108
- unless type || result_from_table.blank?
109
- if find_many
110
- result_from_table.select! { |result| what_to_find.include? result.try(:id) }
111
- else
112
- if result_from_table.id != what_to_find
113
- result_from_table = nil
114
- end
115
- end
113
+
114
+
115
+
116
+ private
117
+
118
+
119
+
120
+ def find_by_ids(*ids, options) # :nodoc:
121
+ raise ArgumentError.new("At least one argument required!") if ids.empty?
122
+
123
+ find_many = ids.first.is_a? Array
124
+ ids = ids.flatten.compact.uniq
125
+
126
+ case ids.length
127
+ when 0
128
+ raise RecordNotFound.new("Can't find a #{model_name.human} without an ID.")
129
+ when 1
130
+ record = find_one(ids.first, options)
131
+ find_many ? [record] : record
132
+ else
133
+ find_some(ids, options)
116
134
  end
135
+ end
117
136
 
118
- raise RecordNotFound.new("Could not find #{model_name} with id=#{what_to_find}") if result_from_table.blank? && type.nil?
119
-
120
- if find_many && !skip_expected_result_check && expected_result_size && expected_result_size != result_from_table.length
121
- raise RecordNotFound.new("Expected to find #{expected_result_size} records, but found only #{result_from_table.length}")
137
+ def find_one(id, options) # :nodoc:
138
+ query_hbase(id, options).first.tap do |record|
139
+ raise RecordNotFound.new("Could not find #{model_name} with id=#{id}") if record.nil? || record.id != id
122
140
  end
123
-
124
- records = [result_from_table].compact.flatten.collect do |row|
125
- instantiate(transpose_hbase_columns_to_record_attributes(row))
141
+ end
142
+
143
+ def find_some(ids, options) # :nodoc:
144
+ expected_result_size = ids.length
145
+
146
+ query_hbase(ids, options).tap do |records|
147
+ records.select! { |record| ids.include?(record.id) }
148
+
149
+ if !options[:skip_expected_result_check] && records.length != expected_result_size
150
+ raise RecordNotFound.new("Expected to find #{expected_result_size} records, but found only #{records.length}")
151
+ end
126
152
  end
153
+ end
127
154
 
128
- find_many ? records : records.first
155
+ def find_all(options) # :nodoc:
156
+ select_known_column_families_if_no_selections_are_added(options)
157
+ query_hbase { table.all(options) }
129
158
  end
130
159
 
131
160
 
161
+ def select_known_column_families_if_no_selections_are_added(options)
162
+ unless options.has_key? :select
163
+ default_selection = known_column_family_names
164
+ options[:select] = default_selection if default_selection.any?
165
+ end
166
+ end
132
167
 
133
168
 
134
- private
135
169
 
136
- def query_hbase(type, args, find_many) # :nodoc:
137
- result_from_table = if type
138
- hbase_query_all_first(type, args)
139
- else
140
- options = args.extract_options!
141
- what_to_find = args.first
142
- expected_result_size = 1
170
+ #
171
+ # Queries hbase. Either looks for what to find with given options
172
+ # or yields the block and uses that as result when instantiate records from rows
173
+ #
174
+ def query_hbase(what_to_find = nil, options = nil) # :nodoc:
175
+ result = if block_given?
176
+ yield
177
+ else
178
+ select_known_column_families_if_no_selections_are_added(options)
179
+ table.find(what_to_find, options)
180
+ end
181
+
182
+ ensure_id_is_utf8_encoded(Array(result).compact).collect do |row|
183
+ instantiate_row_from_hbase(row)
184
+ end
185
+ rescue => e
186
+ if e.is_a?(Apache::Hadoop::Hbase::Thrift::IOError) && e.message =~ /NoSuchColumnFamilyException/
187
+ raise ColumnFamiliesMissingError.new(self, Persistence::Operations::TableOperationHelpers.calculate_missing_family_names(self))
188
+ else
189
+ raise e
190
+ end
191
+ end
143
192
 
144
- if args.first.kind_of?(Array)
145
- find_many = true
146
- elsif args.length > 1
147
- find_many = true
148
- what_to_find = args
149
- end
193
+ def instantiate_row_from_hbase(row)
194
+ instantiate(*transpose_hbase_row_to_record_attributes_and_raw_data(row)) # :nodoc:
195
+ end
150
196
 
151
- expected_result_size = what_to_find.length if what_to_find.is_a? Array
152
- hbase_query_find(what_to_find, options)
153
- end
154
197
 
155
- [find_many, expected_result_size, what_to_find, result_from_table]
156
- end
157
198
 
158
- def hbase_query_all_first(type, args)
159
- table.send(type, *args) # first() / all()
199
+ def instantiate(record, raw_data) # :nodoc:
200
+ model = if record.has_key?(inheritance_attribute)
201
+ if klass = record[inheritance_attribute] and klass.present?
202
+ klass.constantize.allocate
203
+ else
204
+ base_class.allocate
205
+ end
206
+ else
207
+ allocate
208
+ end
209
+
210
+ model.init_with('attributes' => record, 'raw_data' => raw_data)
160
211
  end
161
212
 
162
- def hbase_query_find(what_to_find, options)
163
- table.find(what_to_find, options)
213
+
214
+
215
+ def ensure_id_is_utf8_encoded(result_from_table) # :nodoc
216
+ return nil if result_from_table.nil?
217
+
218
+ if result_from_table.respond_to? :id
219
+ result_from_table.id.force_encoding(Encoding::UTF_8) if result_from_table.id.respond_to? :force_encoding
220
+ elsif result_from_table.respond_to? :each
221
+ result_from_table.collect! { |result| ensure_id_is_utf8_encoded(result) }
222
+ end
223
+
224
+ result_from_table
164
225
  end
165
226
 
166
- def transpose_hbase_columns_to_record_attributes(row) #: nodoc:
227
+ def transpose_hbase_row_to_record_attributes_and_raw_data(row) # :nodoc:
167
228
  attributes = {:id => row.id}
229
+ raw_data = row.values_raw_data_hash
168
230
 
169
231
  autoload_column_families_and_fields_with(row.columns.keys)
170
232
 
171
233
  # Parse the schema to populate the instance attributes
172
234
  attributes_schema.each do |key, field|
173
- cell = row.columns[field.unique_name]
174
- attributes[field.name] = cell.nil? ? nil : field.decode(cell.value)
235
+ data = raw_data.has_key?(field.column_family.name) ? raw_data[field.column_family.name][field.column] : nil
236
+ attributes[field.name] = data.nil? ? nil : field.decode(data.value)
175
237
  end
176
- attributes
177
- end
178
-
179
- def instantiate(record) # :nodoc:
180
- model = if klass = record[inheritance_attribute] and klass.present?
181
- klass.constantize.allocate
182
- else
183
- allocate
184
- end
185
238
 
186
- model.init_with('attributes' => record)
239
+ [attributes, raw_data]
187
240
  end
188
241
  end
189
242
  end
@@ -35,7 +35,7 @@ module MassiveRecord
35
35
  raise error
36
36
  else
37
37
  logger.try :info, "*** TABLE MISSING: Table '#{table_name}' seems to be missing. Will create it, then retry call to find()."
38
- hbase_create_table!
38
+ Persistence::Operations::TableOperationHelpers.hbase_create_table!(self)
39
39
  yield
40
40
  end
41
41
  end
@@ -16,31 +16,34 @@ module MassiveRecord
16
16
  #
17
17
  class Scope
18
18
  MULTI_VALUE_METHODS = %w(select)
19
- SINGLE_VALUE_METHODS = %w(limit)
19
+ SINGLE_VALUE_METHODS = %w(limit starts_with offset)
20
20
 
21
21
  attr_accessor *MULTI_VALUE_METHODS.collect { |m| m + "_values" }
22
22
  attr_accessor *SINGLE_VALUE_METHODS.collect { |m| m + "_value" }
23
- attr_accessor :loaded, :klass
23
+ attr_accessor :loaded, :klass, :extra_finder_options
24
24
  alias :loaded? :loaded
25
25
 
26
26
 
27
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
27
+ delegate :to_xml, :to_yaml, :length, :size, :collect, :map, :each, :all?, :include?, :to => :to_a
28
28
 
29
29
 
30
- def initialize(klass, options = {})
30
+ def initialize(klass)
31
31
  @klass = klass
32
32
  @extra_finder_options = {}
33
33
 
34
34
  reset
35
35
  reset_single_values_options
36
36
  reset_multi_values_options
37
+ end
37
38
 
38
- apply_finder_options(options[:find_options]) if options.has_key? :find_options
39
+ def initialize_copy(old)
40
+ reset
39
41
  end
40
42
 
41
-
43
+
42
44
  def reset
43
45
  @loaded = false
46
+ @records = []
44
47
  end
45
48
 
46
49
 
@@ -50,8 +53,7 @@ module MassiveRecord
50
53
  #
51
54
 
52
55
  def select(*select)
53
- self.select_values |= select.flatten.compact.collect(&:to_s)
54
- self
56
+ cloned_version_with { self.select_values |= select.flatten.compact.collect(&:to_s) }
55
57
  end
56
58
 
57
59
 
@@ -60,8 +62,15 @@ module MassiveRecord
60
62
  #
61
63
 
62
64
  def limit(limit)
63
- self.limit_value = limit
64
- self
65
+ cloned_version_with { self.limit_value = limit }
66
+ end
67
+
68
+ def starts_with(starts_with)
69
+ cloned_version_with { self.starts_with_value = starts_with }
70
+ end
71
+
72
+ def offset(offset)
73
+ cloned_version_with { self.offset_value = offset }
65
74
  end
66
75
 
67
76
 
@@ -83,24 +92,32 @@ module MassiveRecord
83
92
 
84
93
  def find(*args)
85
94
  options = args.extract_options!.to_options
86
- apply_finder_options(options)
87
- args << options.merge(find_options)
88
-
89
- klass.do_find(*args)
95
+
96
+ if options.any?
97
+ apply_finder_options(options).find(*args)
98
+ else
99
+ klass.do_find(*args << find_options)
100
+ end
90
101
  end
91
102
 
92
103
  def all(options = {})
93
- apply_finder_options(options)
94
- to_a
104
+ if options.empty?
105
+ to_a
106
+ else
107
+ apply_finder_options(options).to_a
108
+ end
95
109
  end
96
110
 
97
111
  def first(options = {})
98
- apply_finder_options(options)
99
- limit(1).to_a.first
112
+ if loaded? && options.empty?
113
+ @records.first
114
+ else
115
+ apply_finder_options(options).limit(1).to_a.first
116
+ end
100
117
  end
101
118
 
102
119
  def last(*args)
103
- raise "Sorry, not implemented!"
120
+ raise "Sorry, but query last requires all records to be fetched. If you really want to do this, do an scope.all.last instead."
104
121
  end
105
122
 
106
123
 
@@ -112,11 +129,32 @@ module MassiveRecord
112
129
  @records
113
130
  end
114
131
 
132
+
133
+ #
134
+ # Takes a hash of finder options, applies them to
135
+ # a new scope and returns a that scope.
136
+ #
137
+ def apply_finder_options(options)
138
+ scope = clone
139
+ return scope if options.empty?
140
+
141
+ options.each do |scope_method, arguments|
142
+ if respond_to? scope_method
143
+ scope = scope.send(scope_method, arguments)
144
+ else
145
+ scope.extra_finder_options[scope_method] = arguments
146
+ end
147
+ end
115
148
 
149
+ scope
150
+ end
116
151
 
117
152
 
118
153
  private
119
154
 
155
+ def cloned_version_with(&block)
156
+ clone.tap { |scope| scope.instance_eval(&block) }
157
+ end
120
158
 
121
159
  def load_records
122
160
  @records = klass.do_find(:all, find_options)
@@ -124,6 +162,7 @@ module MassiveRecord
124
162
  @records
125
163
  end
126
164
 
165
+ # Returns find options which adapter's find understands.
127
166
  def find_options
128
167
  options = {}
129
168
 
@@ -138,21 +177,6 @@ module MassiveRecord
138
177
  options.merge(@extra_finder_options)
139
178
  end
140
179
 
141
-
142
-
143
- def apply_finder_options(options)
144
- options.each do |scope_method, arguments|
145
- if respond_to? scope_method
146
- send(scope_method, arguments)
147
- else
148
- @extra_finder_options[scope_method] = arguments
149
- end
150
- end
151
- end
152
-
153
-
154
-
155
-
156
180
  def reset_single_values_options
157
181
  SINGLE_VALUE_METHODS.each { |m| instance_variable_set("@#{m}_value", nil) }
158
182
  end