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.
Files changed (83) hide show
  1. data/CHANGELOG.md +28 -5
  2. data/Gemfile.lock +12 -12
  3. data/README.md +29 -1
  4. data/lib/massive_record/adapters/initialize.rb +18 -0
  5. data/lib/massive_record/adapters/thrift/adapter.rb +25 -0
  6. data/lib/massive_record/adapters/thrift/column_family.rb +24 -0
  7. data/lib/massive_record/adapters/thrift/connection.rb +73 -0
  8. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase.rb +0 -0
  9. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_constants.rb +0 -0
  10. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_types.rb +0 -0
  11. data/lib/massive_record/adapters/thrift/row.rb +150 -0
  12. data/lib/massive_record/adapters/thrift/scanner.rb +59 -0
  13. data/lib/massive_record/adapters/thrift/table.rb +169 -0
  14. data/lib/massive_record/orm/attribute_methods/read.rb +2 -1
  15. data/lib/massive_record/orm/base.rb +61 -3
  16. data/lib/massive_record/orm/coders/chained.rb +71 -0
  17. data/lib/massive_record/orm/coders/json.rb +17 -0
  18. data/lib/massive_record/orm/coders/yaml.rb +15 -0
  19. data/lib/massive_record/orm/coders.rb +3 -0
  20. data/lib/massive_record/orm/errors.rb +15 -2
  21. data/lib/massive_record/orm/finders/scope.rb +166 -0
  22. data/lib/massive_record/orm/finders.rb +45 -24
  23. data/lib/massive_record/orm/persistence.rb +4 -4
  24. data/lib/massive_record/orm/relations/interface.rb +170 -0
  25. data/lib/massive_record/orm/relations/metadata.rb +150 -0
  26. data/lib/massive_record/orm/relations/proxy/references_many.rb +229 -0
  27. data/lib/massive_record/orm/relations/proxy/references_one.rb +40 -0
  28. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +49 -0
  29. data/lib/massive_record/orm/relations/proxy.rb +174 -0
  30. data/lib/massive_record/orm/relations.rb +6 -0
  31. data/lib/massive_record/orm/schema/column_interface.rb +1 -1
  32. data/lib/massive_record/orm/schema/field.rb +62 -27
  33. data/lib/massive_record/orm/single_table_inheritance.rb +21 -0
  34. data/lib/massive_record/version.rb +1 -1
  35. data/lib/massive_record/wrapper/adapter.rb +6 -0
  36. data/lib/massive_record/wrapper/base.rb +6 -7
  37. data/lib/massive_record/wrapper/cell.rb +9 -32
  38. data/lib/massive_record/wrapper/column_families_collection.rb +2 -2
  39. data/lib/massive_record/wrapper/errors.rb +10 -0
  40. data/lib/massive_record/wrapper/tables_collection.rb +1 -1
  41. data/lib/massive_record.rb +5 -12
  42. data/spec/orm/cases/attribute_methods_spec.rb +5 -1
  43. data/spec/orm/cases/base_spec.rb +77 -4
  44. data/spec/orm/cases/column_spec.rb +1 -1
  45. data/spec/orm/cases/finder_default_scope.rb +53 -0
  46. data/spec/orm/cases/finder_scope_spec.rb +288 -0
  47. data/spec/orm/cases/finders_spec.rb +56 -13
  48. data/spec/orm/cases/persistence_spec.rb +20 -5
  49. data/spec/orm/cases/single_table_inheritance_spec.rb +26 -0
  50. data/spec/orm/cases/table_spec.rb +1 -1
  51. data/spec/orm/cases/timestamps_spec.rb +16 -16
  52. data/spec/orm/coders/chained_spec.rb +73 -0
  53. data/spec/orm/coders/json_spec.rb +6 -0
  54. data/spec/orm/coders/yaml_spec.rb +6 -0
  55. data/spec/orm/models/best_friend.rb +7 -0
  56. data/spec/orm/models/friend.rb +4 -0
  57. data/spec/orm/models/person.rb +20 -6
  58. data/spec/orm/models/{person_with_timestamps.rb → person_with_timestamp.rb} +1 -1
  59. data/spec/orm/models/test_class.rb +3 -0
  60. data/spec/orm/relations/interface_spec.rb +207 -0
  61. data/spec/orm/relations/metadata_spec.rb +202 -0
  62. data/spec/orm/relations/proxy/references_many_spec.rb +624 -0
  63. data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +106 -0
  64. data/spec/orm/relations/proxy/references_one_spec.rb +111 -0
  65. data/spec/orm/relations/proxy_spec.rb +13 -0
  66. data/spec/orm/schema/field_spec.rb +101 -2
  67. data/spec/shared/orm/coders/an_orm_coder.rb +14 -0
  68. data/spec/shared/orm/relations/proxy.rb +154 -0
  69. data/spec/shared/orm/relations/singular_proxy.rb +68 -0
  70. data/spec/spec_helper.rb +1 -0
  71. data/spec/thrift/cases/encoding_spec.rb +28 -7
  72. data/spec/wrapper/cases/adapter_spec.rb +9 -0
  73. data/spec/wrapper/cases/connection_spec.rb +13 -10
  74. data/spec/wrapper/cases/table_spec.rb +85 -85
  75. metadata +74 -22
  76. data/TODO.md +0 -8
  77. data/lib/massive_record/exceptions.rb +0 -11
  78. data/lib/massive_record/wrapper/column_family.rb +0 -22
  79. data/lib/massive_record/wrapper/connection.rb +0 -71
  80. data/lib/massive_record/wrapper/row.rb +0 -173
  81. data/lib/massive_record/wrapper/scanner.rb +0 -61
  82. data/lib/massive_record/wrapper/table.rb +0 -149
  83. 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
@@ -0,0 +1,15 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Coders
4
+ class YAML
5
+ def dump(object)
6
+ ::YAML.dump(object)
7
+ end
8
+
9
+ def load(yaml)
10
+ ::YAML.load(yaml)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ require 'massive_record/orm/coders/json'
2
+ require 'massive_record/orm/coders/yaml'
3
+ require 'massive_record/orm/coders/chained'
@@ -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 ? [] : nil) unless table.exists?
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.deserialize_value
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
- allocate.tap do |model|
119
- model.init_with('attributes' => record)
120
- end
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