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
@@ -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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
@@ -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, :
|
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(
|
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
|
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
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
125
|
-
|
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
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
159
|
-
|
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
|
-
|
163
|
-
|
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
|
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
|
-
|
174
|
-
attributes[field.name] =
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
94
|
-
|
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
|
-
|
99
|
-
|
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,
|
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
|