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,17 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
|
3
|
+
module MassiveRecord
|
4
|
+
module ORM
|
5
|
+
module Coders
|
6
|
+
class JSON
|
7
|
+
def dump(object)
|
8
|
+
ActiveSupport::JSON.encode(object)
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(json)
|
12
|
+
ActiveSupport::JSON.decode(json)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -33,9 +33,9 @@ module MassiveRecord
|
|
33
33
|
|
34
34
|
class ColumnFamiliesMissingError < MassiveRecordError
|
35
35
|
attr_reader :missing_column_families
|
36
|
-
def initialize(missing_column_families)
|
36
|
+
def initialize(klass, missing_column_families)
|
37
37
|
@missing_column_families = missing_column_families
|
38
|
-
super("hbase are missing some column families: #{@missing_column_families.join(' ')}. Please migrate the database.")
|
38
|
+
super("hbase are missing some column families for class '#{klass.to_s}', table '#{klass.table_name}': #{@missing_column_families.join(' ')}. Please migrate the database.")
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -52,5 +52,18 @@ module MassiveRecord
|
|
52
52
|
# Raised if you try to save a record which is read only
|
53
53
|
class ReadOnlyRecord < MassiveRecordError
|
54
54
|
end
|
55
|
+
|
56
|
+
|
57
|
+
# Raised when a relation s already defined
|
58
|
+
class RelationAlreadyDefined < MassiveRecordError
|
59
|
+
end
|
60
|
+
|
61
|
+
# Raised if proxy_target in a relation proxy does not match what the proxy expects
|
62
|
+
class RelationTypeMismatch < MassiveRecordError
|
63
|
+
end
|
64
|
+
|
65
|
+
# Raised when an attribute is decoded from the database, but the type returned does not match what is expected
|
66
|
+
class SerializationTypeMismatch < MassiveRecordError
|
67
|
+
end
|
55
68
|
end
|
56
69
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Finders
|
4
|
+
|
5
|
+
#
|
6
|
+
# A finder scope's jobs is to contain and build up
|
7
|
+
# limitations and meta data of a DB query about to being
|
8
|
+
# executed, allowing us to call for instance
|
9
|
+
#
|
10
|
+
# User.select(:info).limit(5) or
|
11
|
+
# User.select(:info).find(a_user_id)
|
12
|
+
#
|
13
|
+
# Each call adds restrictions or info about the query about
|
14
|
+
# to be executed, and the proxy will act as an Enumerable object
|
15
|
+
# when asking for multiple values
|
16
|
+
#
|
17
|
+
class Scope
|
18
|
+
MULTI_VALUE_METHODS = %w(select)
|
19
|
+
SINGLE_VALUE_METHODS = %w(limit)
|
20
|
+
|
21
|
+
attr_accessor *MULTI_VALUE_METHODS.collect { |m| m + "_values" }
|
22
|
+
attr_accessor *SINGLE_VALUE_METHODS.collect { |m| m + "_value" }
|
23
|
+
attr_accessor :loaded, :klass
|
24
|
+
alias :loaded? :loaded
|
25
|
+
|
26
|
+
|
27
|
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
|
28
|
+
|
29
|
+
|
30
|
+
def initialize(klass, options = {})
|
31
|
+
@klass = klass
|
32
|
+
@extra_finder_options = {}
|
33
|
+
|
34
|
+
reset
|
35
|
+
reset_single_values_options
|
36
|
+
reset_multi_values_options
|
37
|
+
|
38
|
+
apply_finder_options(options[:find_options]) if options.has_key? :find_options
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def reset
|
43
|
+
@loaded = false
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
#
|
49
|
+
# Multi value options
|
50
|
+
#
|
51
|
+
|
52
|
+
def select(*select)
|
53
|
+
self.select_values |= select.flatten.compact.collect(&:to_s)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
#
|
59
|
+
# Single value options
|
60
|
+
#
|
61
|
+
|
62
|
+
def limit(limit)
|
63
|
+
self.limit_value = limit
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
def ==(other)
|
72
|
+
case other
|
73
|
+
when Scope
|
74
|
+
object_id == other.object_id
|
75
|
+
when Array
|
76
|
+
to_a == other
|
77
|
+
else
|
78
|
+
raise "Don't know how to compare #{self.class} with #{other.class}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
def find(*args)
|
85
|
+
options = args.extract_options!.to_options
|
86
|
+
apply_finder_options(options)
|
87
|
+
args << options.merge(find_options)
|
88
|
+
|
89
|
+
klass.do_find(*args)
|
90
|
+
end
|
91
|
+
|
92
|
+
def all(options = {})
|
93
|
+
apply_finder_options(options)
|
94
|
+
to_a
|
95
|
+
end
|
96
|
+
|
97
|
+
def first(options = {})
|
98
|
+
apply_finder_options(options)
|
99
|
+
limit(1).to_a.first
|
100
|
+
end
|
101
|
+
|
102
|
+
def last(*args)
|
103
|
+
raise "Sorry, not implemented!"
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
def to_a
|
109
|
+
return @records if loaded?
|
110
|
+
@records = load_records
|
111
|
+
@records = [@records] unless @records.is_a? Array
|
112
|
+
@records
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
|
121
|
+
def load_records
|
122
|
+
@records = klass.do_find(:all, find_options)
|
123
|
+
@loaded = true
|
124
|
+
@records
|
125
|
+
end
|
126
|
+
|
127
|
+
def find_options
|
128
|
+
options = {}
|
129
|
+
|
130
|
+
SINGLE_VALUE_METHODS.each do |m|
|
131
|
+
value = send("#{m}_value") and options[m.to_sym] = value
|
132
|
+
end
|
133
|
+
|
134
|
+
MULTI_VALUE_METHODS.each do |m|
|
135
|
+
values = send("#{m}_values") and values.any? and options[m.to_sym] = values
|
136
|
+
end
|
137
|
+
|
138
|
+
options.merge(@extra_finder_options)
|
139
|
+
end
|
140
|
+
|
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
|
+
def reset_single_values_options
|
157
|
+
SINGLE_VALUE_METHODS.each { |m| instance_variable_set("@#{m}_value", nil) }
|
158
|
+
end
|
159
|
+
|
160
|
+
def reset_multi_values_options
|
161
|
+
MULTI_VALUE_METHODS.each { |m| instance_variable_set("@#{m}_values", []) }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -3,23 +3,30 @@ module MassiveRecord
|
|
3
3
|
module Finders
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
included do
|
7
|
+
class << self
|
8
|
+
delegate :find, :first, :last, :all, :select, :limit, :to => :finder_scope
|
9
|
+
end
|
10
|
+
|
11
|
+
class_attribute :default_scoping, :instance_writer => false
|
12
|
+
end
|
13
|
+
|
6
14
|
module ClassMethods
|
7
|
-
#
|
8
|
-
# Interface for retrieving objects based on key.
|
9
|
-
# Has some convenience behaviour like find :first, :last, :all.
|
10
|
-
#
|
11
|
-
def find(*args)
|
15
|
+
def do_find(*args) # :nodoc:
|
12
16
|
options = args.extract_options!.to_options
|
13
17
|
raise ArgumentError.new("At least one argument required!") if args.empty?
|
14
18
|
raise RecordNotFound.new("Can't find a #{model_name.human} without an ID.") if args.first.nil?
|
15
19
|
raise ArgumentError.new("Sorry, conditions are not supported!") if options.has_key? :conditions
|
20
|
+
|
21
|
+
skip_expected_result_check = options.delete(:skip_expected_result_check)
|
22
|
+
|
16
23
|
args << options
|
17
24
|
|
18
25
|
type = args.shift if args.first.is_a? Symbol
|
19
26
|
find_many = type == :all
|
20
27
|
expected_result_size = nil
|
21
28
|
|
22
|
-
return (find_many ? [] :
|
29
|
+
return (find_many ? [] : raise(RecordNotFound.new("Could not find #{model_name} with id=#{args.first}"))) unless table.exists?
|
23
30
|
|
24
31
|
result_from_table = if type
|
25
32
|
table.send(type, *args) # first() / all()
|
@@ -43,7 +50,7 @@ module MassiveRecord
|
|
43
50
|
# we have no expectations on the returned rows' ids)
|
44
51
|
unless type || result_from_table.blank?
|
45
52
|
if find_many
|
46
|
-
result_from_table.select! { |result| what_to_find.include? result.id }
|
53
|
+
result_from_table.select! { |result| what_to_find.include? result.try(:id) }
|
47
54
|
else
|
48
55
|
if result_from_table.id != what_to_find
|
49
56
|
result_from_table = nil
|
@@ -53,7 +60,7 @@ module MassiveRecord
|
|
53
60
|
|
54
61
|
raise RecordNotFound.new("Could not find #{model_name} with id=#{what_to_find}") if result_from_table.blank? && type.nil?
|
55
62
|
|
56
|
-
if find_many && expected_result_size && expected_result_size != result_from_table.length
|
63
|
+
if find_many && !skip_expected_result_check && expected_result_size && expected_result_size != result_from_table.length
|
57
64
|
raise RecordNotFound.new("Expected to find #{expected_result_size} records, but found only #{result_from_table.length}")
|
58
65
|
end
|
59
66
|
|
@@ -64,19 +71,9 @@ module MassiveRecord
|
|
64
71
|
find_many ? records : records.first
|
65
72
|
end
|
66
73
|
|
67
|
-
def first(*args)
|
68
|
-
find(:first, *args)
|
69
|
-
end
|
70
|
-
|
71
|
-
def last(*args)
|
72
|
-
raise "Sorry, not implemented!"
|
73
|
-
end
|
74
|
-
|
75
|
-
def all(*args)
|
76
|
-
find(:all, *args)
|
77
|
-
end
|
78
|
-
|
79
74
|
def find_in_batches(*args)
|
75
|
+
return unless table.exists?
|
76
|
+
|
80
77
|
table.find_in_batches(*args) do |rows|
|
81
78
|
records = rows.collect do |row|
|
82
79
|
instantiate(transpose_hbase_columns_to_record_attributes(row))
|
@@ -99,6 +96,26 @@ module MassiveRecord
|
|
99
96
|
end
|
100
97
|
|
101
98
|
|
99
|
+
def finder_scope
|
100
|
+
default_scoping || unscoped
|
101
|
+
end
|
102
|
+
|
103
|
+
def default_scope(scope)
|
104
|
+
self.default_scoping = case scope
|
105
|
+
when Scope, nil
|
106
|
+
scope
|
107
|
+
when Hash
|
108
|
+
Scope.new(self, :find_options => scope)
|
109
|
+
else
|
110
|
+
raise "Don't know how to set scope with #{scope.class}."
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def unscoped
|
115
|
+
Scope.new(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
102
119
|
private
|
103
120
|
|
104
121
|
def transpose_hbase_columns_to_record_attributes(row)
|
@@ -109,15 +126,19 @@ module MassiveRecord
|
|
109
126
|
# Parse the schema to populate the instance attributes
|
110
127
|
attributes_schema.each do |key, field|
|
111
128
|
cell = row.columns[field.unique_name]
|
112
|
-
attributes[field.name] = cell.nil? ? nil : cell.
|
129
|
+
attributes[field.name] = cell.nil? ? nil : field.decode(cell.value)
|
113
130
|
end
|
114
131
|
attributes
|
115
132
|
end
|
116
133
|
|
117
134
|
def instantiate(record)
|
118
|
-
|
119
|
-
|
120
|
-
|
135
|
+
model = if klass = record[inheritance_attribute] and klass.present?
|
136
|
+
klass.constantize.allocate
|
137
|
+
else
|
138
|
+
allocate
|
139
|
+
end
|
140
|
+
|
141
|
+
model.init_with('attributes' => record)
|
121
142
|
end
|
122
143
|
end
|
123
144
|
end
|
@@ -30,7 +30,7 @@ module MassiveRecord
|
|
30
30
|
|
31
31
|
|
32
32
|
def reload
|
33
|
-
self.attributes_raw = self.class.find(id).attributes
|
33
|
+
self.attributes_raw = self.class.find(id).attributes if persisted?
|
34
34
|
self
|
35
35
|
end
|
36
36
|
|
@@ -64,7 +64,7 @@ module MassiveRecord
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def destroy
|
67
|
-
@destroyed = row_for_record.destroy and freeze
|
67
|
+
@destroyed = (persisted? ? row_for_record.destroy : true) and freeze
|
68
68
|
end
|
69
69
|
alias_method :delete, :destroy
|
70
70
|
|
@@ -155,7 +155,7 @@ module MassiveRecord
|
|
155
155
|
self.class.table.save
|
156
156
|
end
|
157
157
|
|
158
|
-
raise ColumnFamiliesMissingError.new(calculate_missing_family_names) if !calculate_missing_family_names.empty?
|
158
|
+
raise ColumnFamiliesMissingError.new(self.class, calculate_missing_family_names) if !calculate_missing_family_names.empty?
|
159
159
|
end
|
160
160
|
|
161
161
|
#
|
@@ -190,7 +190,7 @@ module MassiveRecord
|
|
190
190
|
|
191
191
|
attributes_schema.each do |attr_name, orm_field|
|
192
192
|
next unless only_attr_names.empty? || only_attr_names.include?(attr_name)
|
193
|
-
values[orm_field.column_family.name][orm_field.column] = send(attr_name)
|
193
|
+
values[orm_field.column_family.name][orm_field.column] = orm_field.encode(send(attr_name))
|
194
194
|
end
|
195
195
|
|
196
196
|
values
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
module Relations
|
4
|
+
module Interface
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :relations, :instance_writer => false
|
9
|
+
self.relations = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
#
|
15
|
+
# Used to define a references one relation. Example of usage:
|
16
|
+
#
|
17
|
+
# class Person < MassiveRecord::ORM::Table
|
18
|
+
# column_family :info do
|
19
|
+
# field :name
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# references_one :boss, :class_name => "Person", :store_in => :info
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# First argument is the name of the relation. class_name and foreign key is calculated from it, if none given.
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# Options, all optional:
|
29
|
+
#
|
30
|
+
# <tt>class_name</tt>:: Class name is calculated from name, but can be overridden here.
|
31
|
+
# <tt>polymorphic</tt>:: Set it to true for make the association polymorphic. Will use foreign_key,
|
32
|
+
# remove the "_id" (if it's there) and add _type for it's reading/writing of type.
|
33
|
+
# <tt>foreign_key</tt>:: Foreign key is calculated from name suffixed by _id as default.
|
34
|
+
# <tt>store_in</tt>:: Send in the column family to store foreign key in. If none given,
|
35
|
+
# you should define the foreign key method in class if it can be
|
36
|
+
# calculated from another attributes in your class.
|
37
|
+
# <tt>find_with</tt>:: Assign it to a Proc and we will call it with the proxy_owner if you need complete
|
38
|
+
# control over how you retrieve your record.
|
39
|
+
# As a default TargetClass.find(foreign_key_method) is used.
|
40
|
+
#
|
41
|
+
def references_one(name, *args)
|
42
|
+
metadata = set_up_relation('references_one', name, *args)
|
43
|
+
|
44
|
+
create_references_one_accessors(metadata)
|
45
|
+
create_references_one_polymorphic_accessors(metadata) if metadata.polymorphic?
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
#
|
50
|
+
# Used to define a reference many relation. Example of usage:
|
51
|
+
#
|
52
|
+
# class Person < MassiveRecord::ORM::Table
|
53
|
+
# column_family :info do
|
54
|
+
# field :name
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# references_many :cars, :store_in => :info
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# First argument is the name of the relation. class_name and attribute for foreign keys are calculated from it,
|
61
|
+
# if noen given. In the example above Person records will have attribute cars_ids which will be
|
62
|
+
# an array populated with foreign keys.
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# Options, all optional:
|
66
|
+
#
|
67
|
+
# <tt>class_name</tt>:: Class name is calculated from name, but can be overridden here.
|
68
|
+
# <tt>foreign_key</tt>:: Foreign key is calculated from name suffixed by _ids as default.
|
69
|
+
# <tt>store_in</tt>:: Send in the column family to store foreign key in. If none given,
|
70
|
+
# you should define the foreign key method in class if it can be
|
71
|
+
# calculated from another attributes in your class.
|
72
|
+
# <tt>records_starts_from</tt>:: A method name which returns an ID to start from when fetching rows in
|
73
|
+
# Person's table. This is useful if you for instance has a person with id 1
|
74
|
+
# and in your table for cars have cars id like "<person_id>-<incremental number>"
|
75
|
+
# or something. Then you can say references_many :cars, :starts_with => :id.
|
76
|
+
# <tt>find_with</tt>:: Assign it to a Proc and we will call it with the proxy_owner if you need complete
|
77
|
+
# control over how you retrieve your record.
|
78
|
+
# As a default TargetClass.find(foreign_keys_method) is used.
|
79
|
+
#
|
80
|
+
#
|
81
|
+
# Example usage:
|
82
|
+
#
|
83
|
+
# person = Person.first
|
84
|
+
# person.cars # loads and returns all cars.
|
85
|
+
# person.cars.first # Returns first car, either by loading just one object, or return first object in loaded proxy.
|
86
|
+
# person.cars.find("an_id") # Tries to load car with id 1 if that id is among person's cars. Either by a query and look among loaded records
|
87
|
+
# person.cars.limit(3) # Returns the 3 first cars, either by slice the loaded array of cars, or do a limited DB query.
|
88
|
+
#
|
89
|
+
#
|
90
|
+
def references_many(name, *args)
|
91
|
+
metadata = set_up_relation('references_many', name, *args)
|
92
|
+
create_references_many_accessors(metadata)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def set_up_relation(type, name, *args)
|
98
|
+
ensure_relations_exists
|
99
|
+
|
100
|
+
Metadata.new(name, *args).tap do |metadata|
|
101
|
+
metadata.relation_type = type
|
102
|
+
raise RelationAlreadyDefined unless self.relations.add?(metadata)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def ensure_relations_exists
|
107
|
+
self.relations = Set.new if relations.nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def create_references_one_accessors(metadata)
|
112
|
+
redefine_method(metadata.name) do
|
113
|
+
proxy = relation_proxy(metadata.name)
|
114
|
+
proxy.load_proxy_target ? proxy : nil
|
115
|
+
end
|
116
|
+
|
117
|
+
redefine_method(metadata.name+'=') do |record|
|
118
|
+
relation_proxy(metadata.name).replace(record)
|
119
|
+
end
|
120
|
+
|
121
|
+
if metadata.persisting_foreign_key?
|
122
|
+
add_field_to_column_family(metadata.store_in, metadata.foreign_key)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def create_references_one_polymorphic_accessors(metadata)
|
127
|
+
if metadata.persisting_foreign_key?
|
128
|
+
add_field_to_column_family(metadata.store_in, metadata.polymorphic_type_column)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def create_references_many_accessors(metadata)
|
133
|
+
redefine_method(metadata.name) do
|
134
|
+
relation_proxy(metadata.name)
|
135
|
+
end
|
136
|
+
|
137
|
+
if metadata.persisting_foreign_key?
|
138
|
+
add_field_to_column_family(metadata.store_in, metadata.foreign_key, :type => :array, :default => [])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def relation_proxy(name)
|
148
|
+
name = name.to_s
|
149
|
+
|
150
|
+
unless proxy = relation_proxy_get(name)
|
151
|
+
if metadata = relations.find { |meta| meta.name == name }
|
152
|
+
proxy = metadata.new_relation_proxy(self)
|
153
|
+
relation_proxy_set(name, proxy)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
proxy
|
158
|
+
end
|
159
|
+
|
160
|
+
def relation_proxy_get(name)
|
161
|
+
@relation_proxy_cache[name.to_s]
|
162
|
+
end
|
163
|
+
|
164
|
+
def relation_proxy_set(name, proxy)
|
165
|
+
@relation_proxy_cache[name.to_s] = proxy
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|