massive_record 0.1.1 → 0.2.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|