massive_record 0.1.1 → 0.2.0.beta
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 +28 -5
- data/Gemfile.lock +12 -12
- data/README.md +29 -1
- data/lib/massive_record/adapters/initialize.rb +18 -0
- data/lib/massive_record/adapters/thrift/adapter.rb +25 -0
- data/lib/massive_record/adapters/thrift/column_family.rb +24 -0
- data/lib/massive_record/adapters/thrift/connection.rb +73 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase.rb +0 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_constants.rb +0 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_types.rb +0 -0
- data/lib/massive_record/adapters/thrift/row.rb +150 -0
- data/lib/massive_record/adapters/thrift/scanner.rb +59 -0
- data/lib/massive_record/adapters/thrift/table.rb +169 -0
- data/lib/massive_record/orm/attribute_methods/read.rb +2 -1
- data/lib/massive_record/orm/base.rb +61 -3
- data/lib/massive_record/orm/coders/chained.rb +71 -0
- data/lib/massive_record/orm/coders/json.rb +17 -0
- data/lib/massive_record/orm/coders/yaml.rb +15 -0
- data/lib/massive_record/orm/coders.rb +3 -0
- data/lib/massive_record/orm/errors.rb +15 -2
- data/lib/massive_record/orm/finders/scope.rb +166 -0
- data/lib/massive_record/orm/finders.rb +45 -24
- data/lib/massive_record/orm/persistence.rb +4 -4
- data/lib/massive_record/orm/relations/interface.rb +170 -0
- data/lib/massive_record/orm/relations/metadata.rb +150 -0
- data/lib/massive_record/orm/relations/proxy/references_many.rb +229 -0
- data/lib/massive_record/orm/relations/proxy/references_one.rb +40 -0
- data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +49 -0
- data/lib/massive_record/orm/relations/proxy.rb +174 -0
- data/lib/massive_record/orm/relations.rb +6 -0
- data/lib/massive_record/orm/schema/column_interface.rb +1 -1
- data/lib/massive_record/orm/schema/field.rb +62 -27
- data/lib/massive_record/orm/single_table_inheritance.rb +21 -0
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/adapter.rb +6 -0
- data/lib/massive_record/wrapper/base.rb +6 -7
- data/lib/massive_record/wrapper/cell.rb +9 -32
- data/lib/massive_record/wrapper/column_families_collection.rb +2 -2
- data/lib/massive_record/wrapper/errors.rb +10 -0
- data/lib/massive_record/wrapper/tables_collection.rb +1 -1
- data/lib/massive_record.rb +5 -12
- data/spec/orm/cases/attribute_methods_spec.rb +5 -1
- data/spec/orm/cases/base_spec.rb +77 -4
- data/spec/orm/cases/column_spec.rb +1 -1
- data/spec/orm/cases/finder_default_scope.rb +53 -0
- data/spec/orm/cases/finder_scope_spec.rb +288 -0
- data/spec/orm/cases/finders_spec.rb +56 -13
- data/spec/orm/cases/persistence_spec.rb +20 -5
- data/spec/orm/cases/single_table_inheritance_spec.rb +26 -0
- data/spec/orm/cases/table_spec.rb +1 -1
- data/spec/orm/cases/timestamps_spec.rb +16 -16
- data/spec/orm/coders/chained_spec.rb +73 -0
- data/spec/orm/coders/json_spec.rb +6 -0
- data/spec/orm/coders/yaml_spec.rb +6 -0
- data/spec/orm/models/best_friend.rb +7 -0
- data/spec/orm/models/friend.rb +4 -0
- data/spec/orm/models/person.rb +20 -6
- data/spec/orm/models/{person_with_timestamps.rb → person_with_timestamp.rb} +1 -1
- data/spec/orm/models/test_class.rb +3 -0
- data/spec/orm/relations/interface_spec.rb +207 -0
- data/spec/orm/relations/metadata_spec.rb +202 -0
- data/spec/orm/relations/proxy/references_many_spec.rb +624 -0
- data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +106 -0
- data/spec/orm/relations/proxy/references_one_spec.rb +111 -0
- data/spec/orm/relations/proxy_spec.rb +13 -0
- data/spec/orm/schema/field_spec.rb +101 -2
- data/spec/shared/orm/coders/an_orm_coder.rb +14 -0
- data/spec/shared/orm/relations/proxy.rb +154 -0
- data/spec/shared/orm/relations/singular_proxy.rb +68 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/thrift/cases/encoding_spec.rb +28 -7
- data/spec/wrapper/cases/adapter_spec.rb +9 -0
- data/spec/wrapper/cases/connection_spec.rb +13 -10
- data/spec/wrapper/cases/table_spec.rb +85 -85
- metadata +74 -22
- data/TODO.md +0 -8
- data/lib/massive_record/exceptions.rb +0 -11
- data/lib/massive_record/wrapper/column_family.rb +0 -22
- data/lib/massive_record/wrapper/connection.rb +0 -71
- data/lib/massive_record/wrapper/row.rb +0 -173
- data/lib/massive_record/wrapper/scanner.rb +0 -61
- data/lib/massive_record/wrapper/table.rb +0 -149
- data/spec/orm/cases/hbase/connection_spec.rb +0 -13
@@ -0,0 +1,150 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Relations
|
4
|
+
|
5
|
+
#
|
6
|
+
# The master of metadata related to a relation. For instance;
|
7
|
+
# references_one :employee, :foreign_key => "person_id", :class_name => "Person"
|
8
|
+
#
|
9
|
+
class Metadata
|
10
|
+
attr_writer :foreign_key, :store_in, :class_name, :name, :relation_type, :polymorphic
|
11
|
+
attr_accessor :find_with
|
12
|
+
attr_reader :records_starts_from
|
13
|
+
|
14
|
+
def initialize(name, options = {})
|
15
|
+
options.to_options!
|
16
|
+
self.name = name
|
17
|
+
self.foreign_key = options[:foreign_key]
|
18
|
+
if options.has_key? :store_in
|
19
|
+
self.store_in = options[:store_in]
|
20
|
+
elsif options.has_key? :store_foreign_key_in
|
21
|
+
self.store_foreign_key_in = options[:store_foreign_key_in]
|
22
|
+
end
|
23
|
+
self.class_name = options[:class_name]
|
24
|
+
self.find_with = options[:find_with]
|
25
|
+
self.records_starts_from = options[:records_starts_from] if options[:records_starts_from]
|
26
|
+
self.polymorphic = options[:polymorphic]
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def name
|
31
|
+
@name.to_s if @name
|
32
|
+
end
|
33
|
+
|
34
|
+
def relation_type
|
35
|
+
if @relation_type
|
36
|
+
relation_type = @relation_type.to_s
|
37
|
+
relation_type += "_polymorphic" if polymorphic?
|
38
|
+
relation_type
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def foreign_key
|
43
|
+
(@foreign_key || calculate_foreign_key).to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def foreign_key_setter
|
47
|
+
foreign_key+'='
|
48
|
+
end
|
49
|
+
|
50
|
+
def polymorphic_type_column
|
51
|
+
type_column = foreign_key.gsub(/_id$/, '')
|
52
|
+
type_column + "_type"
|
53
|
+
end
|
54
|
+
|
55
|
+
def polymorphic_type_column_setter
|
56
|
+
polymorphic_type_column+'='
|
57
|
+
end
|
58
|
+
|
59
|
+
def class_name
|
60
|
+
(@class_name || calculate_class_name).to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def proxy_target_class
|
64
|
+
class_name.constantize
|
65
|
+
end
|
66
|
+
|
67
|
+
def store_in
|
68
|
+
@store_in.to_s if @store_in
|
69
|
+
end
|
70
|
+
|
71
|
+
def store_foreign_key_in
|
72
|
+
ActiveSupport::Deprecation.warn("store_foreign_key_in is deprecated. Use store_in instead!")
|
73
|
+
store_in
|
74
|
+
end
|
75
|
+
|
76
|
+
def store_foreign_key_in=(column_family)
|
77
|
+
ActiveSupport::Deprecation.warn("store_foreign_key_in is deprecated. Use store_in instead!")
|
78
|
+
self.store_in = column_family
|
79
|
+
end
|
80
|
+
|
81
|
+
def persisting_foreign_key?
|
82
|
+
!!store_in && !records_starts_from
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def polymorphic
|
87
|
+
!!@polymorphic
|
88
|
+
end
|
89
|
+
|
90
|
+
def polymorphic?
|
91
|
+
polymorphic
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def new_relation_proxy(proxy_owner)
|
96
|
+
proxy_class_name.constantize.new(:proxy_owner => proxy_owner, :metadata => self)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def ==(other)
|
101
|
+
other.instance_of?(self.class) && other.hash == hash
|
102
|
+
end
|
103
|
+
alias_method :eql?, :==
|
104
|
+
|
105
|
+
def hash
|
106
|
+
name.hash
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
def represents_a_collection?
|
112
|
+
relation_type == 'references_many'
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def records_starts_from=(method)
|
117
|
+
@records_starts_from = method
|
118
|
+
|
119
|
+
if @records_starts_from
|
120
|
+
self.find_with = Proc.new do |proxy_owner, options = {}|
|
121
|
+
start = proxy_owner.send(records_starts_from) and proxy_target_class.all(options.merge({:start => start}))
|
122
|
+
end
|
123
|
+
else
|
124
|
+
self.find_with = nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
|
132
|
+
def calculate_class_name
|
133
|
+
name.to_s.classify
|
134
|
+
end
|
135
|
+
|
136
|
+
def calculate_foreign_key
|
137
|
+
if represents_a_collection?
|
138
|
+
name.downcase.singularize + "_ids"
|
139
|
+
else
|
140
|
+
name.downcase + "_id"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def proxy_class_name
|
145
|
+
"MassiveRecord::ORM::Relations::Proxy::"+relation_type.classify
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Relations
|
4
|
+
class Proxy
|
5
|
+
class ReferencesMany < Proxy
|
6
|
+
#
|
7
|
+
# Loading proxy_targets will merge it with records found currently in proxy,
|
8
|
+
# to make sure we don't remove any pushed proxy_targets only cause we load the
|
9
|
+
# proxy_targets.
|
10
|
+
#
|
11
|
+
# TODO - Implement methods like:
|
12
|
+
# * find_in_batches
|
13
|
+
# * find_each
|
14
|
+
# * etc :-)
|
15
|
+
#
|
16
|
+
# - A counter cache is also nice.
|
17
|
+
#
|
18
|
+
def load_proxy_target
|
19
|
+
proxy_target_before_load = proxy_target
|
20
|
+
proxy_target_after_load = super
|
21
|
+
|
22
|
+
self.proxy_target = (proxy_target_before_load + proxy_target_after_load).uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
super
|
27
|
+
@proxy_target = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def replace(*records)
|
31
|
+
records.flatten!
|
32
|
+
|
33
|
+
if records.length == 1 and records.first.nil?
|
34
|
+
reset
|
35
|
+
else
|
36
|
+
delete_all
|
37
|
+
concat(records)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
#
|
43
|
+
# Adding record(s) to the collection.
|
44
|
+
#
|
45
|
+
def <<(*records)
|
46
|
+
save_records = proxy_owner.persisted?
|
47
|
+
|
48
|
+
if records.flatten.all? &:valid?
|
49
|
+
records.flatten.each do |record|
|
50
|
+
unless include? record
|
51
|
+
raise_if_type_mismatch(record)
|
52
|
+
add_foreign_key_in_proxy_owner(record.id)
|
53
|
+
proxy_target << record
|
54
|
+
record.save if save_records
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
proxy_owner.save if save_records
|
59
|
+
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias_method :push, :<<
|
64
|
+
alias_method :concat, :<<
|
65
|
+
|
66
|
+
#
|
67
|
+
# Destroy record(s) from the collection
|
68
|
+
# Each record will be asked to destroy itself as well
|
69
|
+
#
|
70
|
+
def destroy(*records)
|
71
|
+
delete_or_destroy *records, :destroy
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Deletes record(s) from the collection
|
76
|
+
#
|
77
|
+
def delete(*records)
|
78
|
+
delete_or_destroy *records, :delete
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Destroys all records
|
83
|
+
#
|
84
|
+
def destroy_all
|
85
|
+
destroy(load_proxy_target)
|
86
|
+
reset
|
87
|
+
loaded!
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Deletes all records from the relationship.
|
92
|
+
# Does not destroy the records
|
93
|
+
#
|
94
|
+
def delete_all
|
95
|
+
delete(load_proxy_target)
|
96
|
+
reset
|
97
|
+
loaded!
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Checks if record is included in collection
|
102
|
+
#
|
103
|
+
# TODO This needs a bit of work, depending on if proxy's proxy_target
|
104
|
+
# has been loaded or not. For now, we are just checking
|
105
|
+
# what we currently have in @proxy_target
|
106
|
+
#
|
107
|
+
def include?(record)
|
108
|
+
load_proxy_target.include? record
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Returns the length of targes
|
113
|
+
#
|
114
|
+
# TODO This can be smarter as well. For instance; if we have not
|
115
|
+
# loaded targets, and we have foreign keys in the owner we
|
116
|
+
# can simply do a owner's foreign keys and ask for it's length.
|
117
|
+
#
|
118
|
+
def length
|
119
|
+
load_proxy_target.length
|
120
|
+
end
|
121
|
+
alias_method :count, :length
|
122
|
+
alias_method :size, :length
|
123
|
+
|
124
|
+
def empty?
|
125
|
+
length == 0
|
126
|
+
end
|
127
|
+
|
128
|
+
def first
|
129
|
+
limit(1).first
|
130
|
+
end
|
131
|
+
|
132
|
+
def find(id)
|
133
|
+
if loaded?
|
134
|
+
record = proxy_target.find { |record| record.id == id }
|
135
|
+
elsif find_with_proc?
|
136
|
+
if id.starts_with? proxy_owner.send(metadata.records_starts_from)
|
137
|
+
record = proxy_target_class.find(id)
|
138
|
+
end
|
139
|
+
elsif foreign_key_in_proxy_owner_exists?(id)
|
140
|
+
record = proxy_target_class.find(id)
|
141
|
+
end
|
142
|
+
|
143
|
+
raise RecordNotFound.new("Could not find #{proxy_target_class.model_name} with id=#{id}") if record.nil?
|
144
|
+
|
145
|
+
record
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Returns a limited result set of target records.
|
150
|
+
#
|
151
|
+
# TODO If we know all our foreign keys (basically we also know our length)
|
152
|
+
# we can then mark our self as loaded if limit is equal to or greater
|
153
|
+
# than foreign keys length.
|
154
|
+
#
|
155
|
+
def limit(limit)
|
156
|
+
if loaded?
|
157
|
+
proxy_target.slice(0, limit)
|
158
|
+
elsif find_with_proc?
|
159
|
+
find_proxy_target_with_proc(:limit => limit)
|
160
|
+
else
|
161
|
+
ids = proxy_owner.send(metadata.foreign_key).slice(0, limit)
|
162
|
+
ids = ids.first if ids.length == 1
|
163
|
+
[find_proxy_target(ids)].flatten
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
|
172
|
+
def delete_or_destroy(*records, method)
|
173
|
+
records.flatten.each do |record|
|
174
|
+
if include? record
|
175
|
+
remove_foreign_key_in_proxy_owner(record.id)
|
176
|
+
proxy_target.delete(record)
|
177
|
+
record.destroy if method.to_sym == :destroy
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
proxy_owner.save if proxy_owner.persisted?
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
|
186
|
+
def find_proxy_target(ids = nil)
|
187
|
+
ids = proxy_owner.send(metadata.foreign_key) if ids.nil?
|
188
|
+
proxy_target_class.find(ids, :skip_expected_result_check => true)
|
189
|
+
end
|
190
|
+
|
191
|
+
def find_proxy_target_with_proc(options = {})
|
192
|
+
[super].compact.flatten
|
193
|
+
end
|
194
|
+
|
195
|
+
def can_find_proxy_target?
|
196
|
+
super || (proxy_owner.respond_to?(metadata.foreign_key) && proxy_owner.send(metadata.foreign_key).any?)
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
|
202
|
+
|
203
|
+
def add_foreign_key_in_proxy_owner(id)
|
204
|
+
if update_foreign_key_fields_in_proxy_owner? && proxy_owner.respond_to?(metadata.foreign_key)
|
205
|
+
proxy_owner.send(metadata.foreign_key) << id
|
206
|
+
notify_of_change_in_proxy_owner_foreign_key
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def remove_foreign_key_in_proxy_owner(id)
|
211
|
+
if proxy_owner.respond_to? metadata.foreign_key
|
212
|
+
proxy_owner.send(metadata.foreign_key).delete(id)
|
213
|
+
notify_of_change_in_proxy_owner_foreign_key
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def foreign_key_in_proxy_owner_exists?(id)
|
218
|
+
proxy_owner.send(metadata.foreign_key).include? id
|
219
|
+
end
|
220
|
+
|
221
|
+
def notify_of_change_in_proxy_owner_foreign_key
|
222
|
+
method = metadata.foreign_key+"_will_change!"
|
223
|
+
proxy_owner.send(method) if proxy_owner.respond_to? method
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Relations
|
4
|
+
class Proxy
|
5
|
+
#
|
6
|
+
# Proxy used to reference one other object in another table.
|
7
|
+
#
|
8
|
+
class ReferencesOne < Proxy
|
9
|
+
|
10
|
+
def proxy_target=(proxy_target)
|
11
|
+
set_foreign_key_in_proxy_owner(proxy_target.id) if proxy_target
|
12
|
+
super(proxy_target)
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace(proxy_target)
|
16
|
+
super
|
17
|
+
set_foreign_key_in_proxy_owner(nil) if proxy_target.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def find_proxy_target
|
24
|
+
proxy_target_class.find(proxy_owner.send(metadata.foreign_key))
|
25
|
+
end
|
26
|
+
|
27
|
+
def can_find_proxy_target?
|
28
|
+
super || proxy_owner.send(metadata.foreign_key).present?
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_foreign_key_in_proxy_owner(id)
|
32
|
+
if update_foreign_key_fields_in_proxy_owner? && proxy_owner.respond_to?(metadata.foreign_key_setter)
|
33
|
+
proxy_owner.send(metadata.foreign_key_setter, id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Relations
|
4
|
+
class Proxy
|
5
|
+
class ReferencesOnePolymorphic < Proxy
|
6
|
+
def proxy_target=(proxy_target)
|
7
|
+
set_foreign_key_and_type_in_proxy_owner(proxy_target.id, proxy_target.class.to_s.underscore) if proxy_target
|
8
|
+
super(proxy_target)
|
9
|
+
end
|
10
|
+
|
11
|
+
def proxy_target_class
|
12
|
+
proxy_owner.send(metadata.polymorphic_type_column).classify.constantize
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace(proxy_target)
|
16
|
+
super
|
17
|
+
set_foreign_key_and_type_in_proxy_owner(nil, nil) if proxy_target.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def find_proxy_target
|
24
|
+
proxy_target_class.find(proxy_owner.send(metadata.foreign_key))
|
25
|
+
end
|
26
|
+
|
27
|
+
def can_find_proxy_target?
|
28
|
+
super || (proxy_owner.send(metadata.foreign_key).present? && proxy_owner.send(metadata.polymorphic_type_column).present?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_foreign_key_and_type_in_proxy_owner(id, type)
|
32
|
+
if update_foreign_key_fields_in_proxy_owner?
|
33
|
+
proxy_owner.send(metadata.foreign_key_setter, id) if proxy_owner.respond_to?(metadata.foreign_key_setter)
|
34
|
+
proxy_owner.send(metadata.polymorphic_type_column_setter, type) if proxy_owner.respond_to?(metadata.polymorphic_type_column_setter)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def raise_if_type_mismatch(record)
|
43
|
+
# By nature this can't be checked, as it should acept all types.
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|