bigrecord 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +44 -0
  3. data/Rakefile +17 -0
  4. data/VERSION +1 -0
  5. data/doc/bigrecord_specs.rdoc +36 -0
  6. data/doc/getting_started.rdoc +157 -0
  7. data/examples/bigrecord.yml +25 -0
  8. data/generators/bigrecord/bigrecord_generator.rb +17 -0
  9. data/generators/bigrecord/templates/bigrecord.rake +47 -0
  10. data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
  11. data/generators/bigrecord_migration/templates/migration.rb +9 -0
  12. data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
  13. data/generators/bigrecord_model/templates/migration.rb +13 -0
  14. data/generators/bigrecord_model/templates/model.rb +7 -0
  15. data/generators/bigrecord_model/templates/model_spec.rb +12 -0
  16. data/init.rb +9 -0
  17. data/install.rb +22 -0
  18. data/lib/big_record/abstract_base.rb +1088 -0
  19. data/lib/big_record/action_view_extensions.rb +266 -0
  20. data/lib/big_record/ar_associations/association_collection.rb +194 -0
  21. data/lib/big_record/ar_associations/association_proxy.rb +158 -0
  22. data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
  23. data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
  24. data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
  25. data/lib/big_record/ar_associations/has_many_association.rb +191 -0
  26. data/lib/big_record/ar_associations/has_one_association.rb +80 -0
  27. data/lib/big_record/ar_associations.rb +1608 -0
  28. data/lib/big_record/ar_reflection.rb +223 -0
  29. data/lib/big_record/attribute_methods.rb +75 -0
  30. data/lib/big_record/base.rb +618 -0
  31. data/lib/big_record/br_associations/association_collection.rb +194 -0
  32. data/lib/big_record/br_associations/association_proxy.rb +153 -0
  33. data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
  34. data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
  35. data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
  36. data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
  37. data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
  38. data/lib/big_record/br_associations/has_one_association.rb +80 -0
  39. data/lib/big_record/br_associations.rb +978 -0
  40. data/lib/big_record/br_reflection.rb +151 -0
  41. data/lib/big_record/callbacks.rb +367 -0
  42. data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
  43. data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
  44. data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
  45. data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
  46. data/lib/big_record/connection_adapters/column.rb +491 -0
  47. data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
  48. data/lib/big_record/connection_adapters/view.rb +27 -0
  49. data/lib/big_record/connection_adapters.rb +10 -0
  50. data/lib/big_record/deletion.rb +73 -0
  51. data/lib/big_record/dynamic_schema.rb +92 -0
  52. data/lib/big_record/embedded.rb +71 -0
  53. data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
  54. data/lib/big_record/family_span_columns.rb +89 -0
  55. data/lib/big_record/fixtures.rb +1025 -0
  56. data/lib/big_record/migration.rb +380 -0
  57. data/lib/big_record/routing_ext.rb +65 -0
  58. data/lib/big_record/timestamp.rb +51 -0
  59. data/lib/big_record/validations.rb +830 -0
  60. data/lib/big_record.rb +125 -0
  61. data/lib/bigrecord.rb +1 -0
  62. data/rails/init.rb +9 -0
  63. data/spec/connections/bigrecord.yml +13 -0
  64. data/spec/connections/cassandra/connection.rb +2 -0
  65. data/spec/connections/hbase/connection.rb +2 -0
  66. data/spec/debug.log +281 -0
  67. data/spec/integration/br_associations_spec.rb +80 -0
  68. data/spec/lib/animal.rb +12 -0
  69. data/spec/lib/book.rb +10 -0
  70. data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
  71. data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
  72. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
  73. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
  74. data/spec/lib/company.rb +14 -0
  75. data/spec/lib/embedded/web_link.rb +12 -0
  76. data/spec/lib/employee.rb +33 -0
  77. data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
  78. data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
  79. data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
  80. data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
  81. data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
  82. data/spec/lib/novel.rb +5 -0
  83. data/spec/lib/zoo.rb +17 -0
  84. data/spec/spec.opts +4 -0
  85. data/spec/spec_helper.rb +55 -0
  86. data/spec/unit/abstract_base_spec.rb +287 -0
  87. data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
  88. data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
  89. data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
  90. data/spec/unit/ar_associations_spec.rb +8 -0
  91. data/spec/unit/base_spec.rb +6 -0
  92. data/spec/unit/br_associations_spec.rb +58 -0
  93. data/spec/unit/embedded_spec.rb +43 -0
  94. data/spec/unit/find_spec.rb +34 -0
  95. data/spec/unit/hash_helper_spec.rb +44 -0
  96. data/spec/unit/migration_spec.rb +144 -0
  97. data/spec/unit/model_spec.rb +315 -0
  98. data/spec/unit/validations_spec.rb +182 -0
  99. data/tasks/bigrecord_tasks.rake +47 -0
  100. data/tasks/data_store.rb +46 -0
  101. data/tasks/gem.rb +22 -0
  102. data/tasks/rdoc.rb +8 -0
  103. data/tasks/spec.rb +34 -0
  104. metadata +189 -0
@@ -0,0 +1,194 @@
1
+ require 'set'
2
+
3
+ module BigRecord
4
+ module BrAssociations
5
+ class AssociationCollection < AssociationProxy #:nodoc:
6
+ def to_ary
7
+ load_target
8
+ @target.to_ary
9
+ end
10
+
11
+ def reset
12
+ @loaded = false
13
+ reset_target!
14
+ end
15
+
16
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
17
+ # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
18
+ def <<(*records)
19
+ result = true
20
+ load_target
21
+
22
+ # HbaseAdapter doesn't support transactions
23
+ @owner.transaction do
24
+ flatten_deeper(records).each do |record|
25
+ raise_on_type_mismatch(record)
26
+ callback(:before_add, record)
27
+ result &&= insert_record(record) unless @owner.new_record?
28
+ @target << record
29
+ callback(:after_add, record)
30
+ end
31
+ end
32
+
33
+ result && self
34
+ end
35
+
36
+ alias_method :push, :<<
37
+ alias_method :concat, :<<
38
+
39
+ # Remove all records from this association
40
+ def delete_all
41
+ load_target
42
+ delete(@target)
43
+ reset_target!
44
+ end
45
+
46
+ # Calculate sum using SQL, not Enumerable
47
+ def sum(*args, &block)
48
+ calculate(:sum, *args, &block)
49
+ end
50
+
51
+ # Remove +records+ from this association. Does not destroy +records+.
52
+ def delete(*records)
53
+ records = flatten_deeper(records)
54
+ records.each { |record| raise_on_type_mismatch(record) }
55
+ records.reject! { |record| @target.delete(record) if record.new_record? }
56
+ return if records.empty?
57
+
58
+ # HbaseAdapter doesn't support transactions
59
+ @owner.transaction do
60
+ records.each { |record| callback(:before_remove, record) }
61
+ delete_records(records)
62
+ records.each do |record|
63
+ @target.delete(record)
64
+ callback(:after_remove, record)
65
+ end
66
+ end
67
+ end
68
+
69
+ # Removes all records from this association. Returns +self+ so method calls may be chained.
70
+ def clear
71
+ return self if length.zero? # forces load_target if hasn't happened already
72
+
73
+ if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all
74
+ destroy_all
75
+ else
76
+ delete_all
77
+ end
78
+
79
+ self
80
+ end
81
+
82
+ def destroy_all
83
+ # HbaseAdapter doesn't support transactions
84
+ @owner.transaction do
85
+ each { |record| record.destroy }
86
+ end
87
+
88
+ reset_target!
89
+ end
90
+
91
+ def create(attributes = {})
92
+ # Can't use Base.create since the foreign key may be a protected attribute.
93
+ if attributes.is_a?(Array)
94
+ attributes.collect { |attr| create(attr) }
95
+ else
96
+ record = build(attributes)
97
+ record.save unless @owner.new_record?
98
+ record
99
+ end
100
+ end
101
+
102
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
103
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
104
+ # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
105
+ def size
106
+ if loaded? && !@reflection.options[:uniq]
107
+ @target.size
108
+ elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
109
+ unsaved_records = Array(@target.detect { |r| r.new_record? })
110
+ unsaved_records.size + count_records
111
+ else
112
+ count_records
113
+ end
114
+ end
115
+
116
+ # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
117
+ # whether the collection is empty, use collection.length.zero? instead of collection.empty?
118
+ def length
119
+ load_target.size
120
+ end
121
+
122
+ def empty?
123
+ size.zero?
124
+ end
125
+
126
+ def uniq(collection = self)
127
+ seen = Set.new
128
+ collection.inject([]) do |kept, record|
129
+ unless seen.include?(record.id)
130
+ kept << record
131
+ seen << record.id
132
+ end
133
+ kept
134
+ end
135
+ end
136
+
137
+ # Replace this collection with +other_array+
138
+ # This will perform a diff and delete/add only records that have changed.
139
+ def replace(other_array)
140
+ other_array.each { |val| raise_on_type_mismatch(val) }
141
+
142
+ load_target
143
+ other = other_array.size < 100 ? other_array : other_array.to_set
144
+ current = @target.size < 100 ? @target : @target.to_set
145
+
146
+ # HbaseAdapter doesn't support transactions
147
+ @owner.transaction do
148
+ delete(@target.select { |v| !other.include?(v) })
149
+ concat(other_array.select { |v| !current.include?(v) })
150
+ end
151
+ end
152
+
153
+ protected
154
+ def reset_target!
155
+ @target = Array.new
156
+ end
157
+
158
+ def find_target
159
+ records =
160
+ if @reflection.options[:finder_sql]
161
+ @reflection.klass.find_by_sql(@finder_sql)
162
+ else
163
+ find(:all)
164
+ end
165
+
166
+ @reflection.options[:uniq] ? uniq(records) : records
167
+ end
168
+
169
+ private
170
+ def callback(method, record)
171
+ callbacks_for(method).each do |callback|
172
+ case callback
173
+ when Symbol
174
+ @owner.send(callback, record)
175
+ when Proc, Method
176
+ callback.call(@owner, record)
177
+ else
178
+ if callback.respond_to?(method)
179
+ callback.send(method, @owner, record)
180
+ else
181
+ raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def callbacks_for(callback_name)
188
+ full_callback_name = "#{callback_name}_for_#{@reflection.name}"
189
+ @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
190
+ end
191
+
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,153 @@
1
+ module BigRecord
2
+ module BrAssociations
3
+ class AssociationProxy #:nodoc:
4
+ attr_reader :reflection
5
+ alias_method :proxy_respond_to?, :respond_to?
6
+ alias_method :proxy_extend, :extend
7
+ delegate :to_param, :to => :proxy_target
8
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
9
+
10
+ def initialize(owner, reflection)
11
+ @owner, @reflection = owner, reflection
12
+ Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
13
+ reset
14
+ end
15
+
16
+ def proxy_owner
17
+ @owner
18
+ end
19
+
20
+ def proxy_reflection
21
+ @reflection
22
+ end
23
+
24
+ def proxy_target
25
+ @target
26
+ end
27
+
28
+ def respond_to?(symbol, include_priv = false)
29
+ proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
30
+ end
31
+
32
+ # Explicitly proxy === because the instance method removal above
33
+ # doesn't catch it.
34
+ def ===(other)
35
+ load_target
36
+ other === @target
37
+ end
38
+
39
+ def aliased_table_name
40
+ @reflection.klass.table_name
41
+ end
42
+
43
+ def reset
44
+ @loaded = false
45
+ @target = nil
46
+ end
47
+
48
+ def reload
49
+ reset
50
+ load_target
51
+ end
52
+
53
+ def loaded?
54
+ @loaded
55
+ end
56
+
57
+ def loaded
58
+ @loaded = true
59
+ end
60
+
61
+ def target
62
+ @target
63
+ end
64
+
65
+ def target=(target)
66
+ @target = target
67
+ loaded
68
+ end
69
+
70
+ protected
71
+ def dependent?
72
+ @reflection.options[:dependent] || false
73
+ end
74
+
75
+ def quoted_record_ids(records)
76
+ records.map { |record| record.quoted_id }.join(',')
77
+ end
78
+
79
+ # def interpolate_sql_options!(options, *keys)
80
+ # keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
81
+ # end
82
+ #
83
+ # def interpolate_sql(sql, record = nil)
84
+ # @owner.send(:interpolate_sql, sql, record)
85
+ # end
86
+ #
87
+ # def sanitize_sql(sql)
88
+ # @reflection.klass.send(:sanitize_sql, sql)
89
+ # end
90
+
91
+ def extract_options_from_args!(args)
92
+ @owner.send(:extract_options_from_args!, args)
93
+ end
94
+
95
+ def set_belongs_to_association_for(record)
96
+ if @reflection.options[:as]
97
+ record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
98
+ record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
99
+ else
100
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
101
+ end
102
+ end
103
+
104
+ def merge_options_from_reflection!(options)
105
+ options.reverse_merge!(
106
+ :group => @reflection.options[:group],
107
+ :limit => @reflection.options[:limit],
108
+ :offset => @reflection.options[:offset],
109
+ :joins => @reflection.options[:joins],
110
+ :include => @reflection.options[:include],
111
+ :select => @reflection.options[:select]
112
+ )
113
+ end
114
+
115
+ private
116
+ def method_missing(method, *args, &block)
117
+ if load_target
118
+ @target.send(method, *args, &block)
119
+ end
120
+ end
121
+
122
+ def load_target
123
+ return nil unless defined?(@loaded)
124
+
125
+ if !loaded? and (!@owner.new_record? || foreign_key_present)
126
+ @target = find_target
127
+ end
128
+
129
+ @loaded = true
130
+ @target
131
+ rescue BigRecord::RecordNotFound
132
+ reset
133
+ end
134
+
135
+ # Can be overwritten by associations that might have the foreign key available for an association without
136
+ # having the object itself (and still being a new record). Currently, only belongs_to present this scenario.
137
+ def foreign_key_present
138
+ false
139
+ end
140
+
141
+ def raise_on_type_mismatch(record)
142
+ unless record.is_a?(@reflection.klass)
143
+ raise BigRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
144
+ end
145
+ end
146
+
147
+ # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
148
+ def flatten_deeper(array)
149
+ array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,52 @@
1
+ module BigRecord
2
+ module BrAssociations
3
+ class BelongsToAssociation < AssociationProxy #:nodoc:
4
+ def create(attributes = {})
5
+ replace(@reflection.klass.create(attributes))
6
+ end
7
+
8
+ def build(attributes = {})
9
+ replace(@reflection.klass.new(attributes))
10
+ end
11
+
12
+ def replace(record)
13
+ counter_cache_name = @reflection.counter_cache_column
14
+
15
+ if record.nil?
16
+ if counter_cache_name && @owner[counter_cache_name] && !@owner.new_record?
17
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
18
+ end
19
+
20
+ @target = @owner[@reflection.primary_key_name] = nil
21
+ else
22
+ raise_on_type_mismatch(record)
23
+
24
+ if counter_cache_name && !@owner.new_record?
25
+ @reflection.klass.increment_counter(counter_cache_name, record.id)
26
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
27
+ end
28
+
29
+ @target = (AssociationProxy === record ? record.target : record)
30
+ @owner[@reflection.primary_key_name] = record.id unless record.new_record?
31
+ @updated = true
32
+ end
33
+
34
+ loaded
35
+ record
36
+ end
37
+
38
+ def updated?
39
+ @updated
40
+ end
41
+
42
+ private
43
+ def find_target
44
+ @reflection.klass.find(@owner[@reflection.primary_key_name])
45
+ end
46
+
47
+ def foreign_key_present
48
+ !@owner[@reflection.primary_key_name].nil?
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,293 @@
1
+ module BigRecord
2
+ module BrAssociations
3
+ class BelongsToManyAssociation < AssociationProxy #:nodoc:
4
+ def to_ary
5
+ load_target
6
+ @target.to_ary
7
+ end
8
+
9
+ def reset
10
+ @loaded = false
11
+ reset_target!
12
+ end
13
+
14
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
15
+ # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
16
+ def <<(*records)
17
+ result = true
18
+ load_target
19
+
20
+ flatten_deeper(records).each do |record|
21
+ raise_on_type_mismatch(record)
22
+ callback(:before_add, record)
23
+ result &&= insert_record(record)
24
+ @target << record
25
+ callback(:after_add, record)
26
+ end
27
+
28
+ result && self
29
+ end
30
+
31
+ alias_method :push, :<<
32
+ alias_method :concat, :<<
33
+
34
+ # Remove all records from this association
35
+ def delete_all
36
+ load_target
37
+ delete(@target)
38
+ reset_target!
39
+ end
40
+
41
+ # # Calculate sum using SQL, not Enumerable
42
+ # def sum(*args, &block)
43
+ # calculate(:sum, *args, &block)
44
+ # end
45
+
46
+ # Remove +records+ from this association. Does not destroy +records+.
47
+ def delete(*records)
48
+ records = flatten_deeper(records)
49
+ records.each { |record| raise_on_type_mismatch(record) }
50
+ records.reject! { |record| @target.delete(record) if record.new_record? }
51
+ return if records.empty?
52
+
53
+ records.each { |record| callback(:before_remove, record) }
54
+ delete_records(records)
55
+ records.each do |record|
56
+ @target.delete(record)
57
+ callback(:after_remove, record)
58
+ end
59
+ end
60
+
61
+ # Removes all records from this association. Returns +self+ so method calls may be chained.
62
+ def clear
63
+ return self if length.zero? # forces load_target if hasn't happened already
64
+
65
+ if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all
66
+ destroy_all
67
+ else
68
+ delete_all
69
+ end
70
+
71
+ self
72
+ end
73
+
74
+ def destroy_all
75
+ # HbaseAdapter doesn't support transactions
76
+ # @owner.transaction do
77
+ each { |record| record.destroy }
78
+ # end
79
+
80
+ reset_target!
81
+ end
82
+
83
+ def create(attributes = {})
84
+ # Can't use Base.create since the foreign key may be a protected attribute.
85
+ if attributes.is_a?(Array)
86
+ attributes.collect { |attr| create(attr) }
87
+ else
88
+ record = build(attributes)
89
+ record.save unless @owner.new_record?
90
+ record
91
+ end
92
+ end
93
+
94
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
95
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
96
+ # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
97
+ def size
98
+ @target.size
99
+ end
100
+
101
+ # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
102
+ # whether the collection is empty, use collection.length.zero? instead of collection.empty?
103
+ def length
104
+ load_target.size
105
+ end
106
+
107
+ def empty?
108
+ size.zero?
109
+ end
110
+
111
+ def uniq(collection = self)
112
+ seen = Set.new
113
+ collection.inject([]) do |kept, record|
114
+ unless seen.include?(record.id)
115
+ kept << record
116
+ seen << record.id
117
+ end
118
+ kept
119
+ end
120
+ end
121
+
122
+ # Replace this collection with +other_array+
123
+ # This will perform a diff and delete/add only records that have changed.
124
+ def replace(other_array)
125
+ if other_array.nil?
126
+ @target = @owner[@reflection.primary_key_name] = nil
127
+ elsif other_array.empty?
128
+ @target = @owner[@reflection.primary_key_name] = []
129
+ else
130
+ other_array.each { |val| raise_on_type_mismatch(val) }
131
+
132
+ load_target
133
+ other = other_array.size < 100 ? other_array : other_array.to_set
134
+ current = @target.size < 100 ? @target : @target.to_set
135
+
136
+ delete(@target.select { |v| !other.include?(v) })
137
+ concat(other_array.select { |v| !current.include?(v) })
138
+ @updated = true
139
+ end
140
+ self
141
+ end
142
+
143
+ def updated?
144
+ @updated
145
+ end
146
+
147
+ protected
148
+ def reset_target!
149
+ @target = Array.new
150
+ end
151
+
152
+ def find_target
153
+ records = find(:all)
154
+ @reflection.options[:uniq] ? uniq(records) : records
155
+ end
156
+
157
+ private
158
+ def callback(method, record)
159
+ callbacks_for(method).each do |callback|
160
+ case callback
161
+ when Symbol
162
+ @owner.send(callback, record)
163
+ when Proc, Method
164
+ callback.call(@owner, record)
165
+ else
166
+ if callback.respond_to?(method)
167
+ callback.send(method, @owner, record)
168
+ else
169
+ raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ def callbacks_for(callback_name)
176
+ full_callback_name = "#{callback_name}_for_#{@reflection.name}"
177
+ @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
178
+ end
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+ public
187
+ def build(attributes = {})
188
+ if attributes.is_a?(Array)
189
+ attributes.collect { |attr| build(attr) }
190
+ else
191
+ record = @reflection.klass.new(attributes)
192
+ set_belongs_to_association_for(record)
193
+
194
+ @target ||= [] unless loaded?
195
+ @target << record
196
+
197
+ record
198
+ end
199
+ end
200
+
201
+ # Count the number of associated records. All arguments are optional.
202
+ def count(*args)
203
+ load_target
204
+ @target.size
205
+ end
206
+
207
+ def find(*args)
208
+ raise ArgumentError, "Only find(:all) is supported" unless args == [:all]
209
+ return [] if @owner[@reflection.primary_key_name].blank?
210
+
211
+ if @reflection.options[:cache]
212
+ # Create the items proxies using the content that is cached
213
+ records = []
214
+ @owner[@reflection.primary_key_name].each do |id|
215
+ records << CachedItemProxyFactory.instance.create(id, @owner, @reflection)
216
+ end
217
+ records
218
+ else
219
+ # Don't throw an exception when the records are not found
220
+ records = []
221
+ @owner[@reflection.primary_key_name].each do |id|
222
+ begin
223
+ records << @reflection.klass.find(id)
224
+ rescue BigRecord::RecordNotFound
225
+ # do nothing
226
+ end
227
+ end
228
+ records
229
+ end
230
+ end
231
+
232
+ protected
233
+ def load_target
234
+ begin
235
+ if !loaded?
236
+ if @target.is_a?(Array) && @target.any?
237
+ @target = (find_target + @target).uniq
238
+ else
239
+ @target = find_target
240
+ end
241
+ end
242
+ rescue ActiveRecord::RecordNotFound, BigRecord::RecordNotFound
243
+ reset
244
+ end
245
+
246
+ loaded if target
247
+ target
248
+ end
249
+
250
+ def count_records
251
+ count = if has_cached_counter?
252
+ @owner.send(:read_attribute, cached_counter_attribute_name)
253
+ elsif @reflection.options[:counter_sql]
254
+ @reflection.klass.count_by_sql(@counter_sql)
255
+ else
256
+ @reflection.klass.count(:conditions => @counter_sql)
257
+ end
258
+
259
+ @target = [] and loaded if count == 0
260
+
261
+ if @reflection.options[:limit]
262
+ count = [ @reflection.options[:limit], count ].min
263
+ end
264
+
265
+ return count
266
+ end
267
+
268
+ def has_cached_counter?
269
+ @owner.attribute_present?(cached_counter_attribute_name)
270
+ end
271
+
272
+ def cached_counter_attribute_name
273
+ "#{@reflection.name}_count"
274
+ end
275
+
276
+ def insert_record(record)
277
+ @owner[@reflection.primary_key_name] ||= []
278
+ @owner[@reflection.primary_key_name] << record.id unless record.new_record?
279
+ end
280
+
281
+ def delete_records(records)
282
+ if @owner[@reflection.primary_key_name]
283
+ records.each{|r| @owner[@reflection.primary_key_name].delete(r.id)}
284
+ end
285
+ end
286
+
287
+ def target_obsolete?
288
+ false
289
+ end
290
+
291
+ end
292
+ end
293
+ end