bigrecord 0.0.5
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/MIT-LICENSE +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/doc/bigrecord_specs.rdoc +36 -0
- data/doc/getting_started.rdoc +157 -0
- data/examples/bigrecord.yml +25 -0
- data/generators/bigrecord/bigrecord_generator.rb +17 -0
- data/generators/bigrecord/templates/bigrecord.rake +47 -0
- data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
- data/generators/bigrecord_migration/templates/migration.rb +9 -0
- data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
- data/generators/bigrecord_model/templates/migration.rb +13 -0
- data/generators/bigrecord_model/templates/model.rb +7 -0
- data/generators/bigrecord_model/templates/model_spec.rb +12 -0
- data/init.rb +9 -0
- data/install.rb +22 -0
- data/lib/big_record/abstract_base.rb +1088 -0
- data/lib/big_record/action_view_extensions.rb +266 -0
- data/lib/big_record/ar_associations/association_collection.rb +194 -0
- data/lib/big_record/ar_associations/association_proxy.rb +158 -0
- data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
- data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
- data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
- data/lib/big_record/ar_associations/has_many_association.rb +191 -0
- data/lib/big_record/ar_associations/has_one_association.rb +80 -0
- data/lib/big_record/ar_associations.rb +1608 -0
- data/lib/big_record/ar_reflection.rb +223 -0
- data/lib/big_record/attribute_methods.rb +75 -0
- data/lib/big_record/base.rb +618 -0
- data/lib/big_record/br_associations/association_collection.rb +194 -0
- data/lib/big_record/br_associations/association_proxy.rb +153 -0
- data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
- data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
- data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
- data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
- data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
- data/lib/big_record/br_associations/has_one_association.rb +80 -0
- data/lib/big_record/br_associations.rb +978 -0
- data/lib/big_record/br_reflection.rb +151 -0
- data/lib/big_record/callbacks.rb +367 -0
- data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
- data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
- data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
- data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
- data/lib/big_record/connection_adapters/column.rb +491 -0
- data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
- data/lib/big_record/connection_adapters/view.rb +27 -0
- data/lib/big_record/connection_adapters.rb +10 -0
- data/lib/big_record/deletion.rb +73 -0
- data/lib/big_record/dynamic_schema.rb +92 -0
- data/lib/big_record/embedded.rb +71 -0
- data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
- data/lib/big_record/family_span_columns.rb +89 -0
- data/lib/big_record/fixtures.rb +1025 -0
- data/lib/big_record/migration.rb +380 -0
- data/lib/big_record/routing_ext.rb +65 -0
- data/lib/big_record/timestamp.rb +51 -0
- data/lib/big_record/validations.rb +830 -0
- data/lib/big_record.rb +125 -0
- data/lib/bigrecord.rb +1 -0
- data/rails/init.rb +9 -0
- data/spec/connections/bigrecord.yml +13 -0
- data/spec/connections/cassandra/connection.rb +2 -0
- data/spec/connections/hbase/connection.rb +2 -0
- data/spec/debug.log +281 -0
- data/spec/integration/br_associations_spec.rb +80 -0
- data/spec/lib/animal.rb +12 -0
- data/spec/lib/book.rb +10 -0
- data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
- data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
- data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
- data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
- data/spec/lib/company.rb +14 -0
- data/spec/lib/embedded/web_link.rb +12 -0
- data/spec/lib/employee.rb +33 -0
- data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
- data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
- data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
- data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
- data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
- data/spec/lib/novel.rb +5 -0
- data/spec/lib/zoo.rb +17 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/unit/abstract_base_spec.rb +287 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
- data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
- data/spec/unit/ar_associations_spec.rb +8 -0
- data/spec/unit/base_spec.rb +6 -0
- data/spec/unit/br_associations_spec.rb +58 -0
- data/spec/unit/embedded_spec.rb +43 -0
- data/spec/unit/find_spec.rb +34 -0
- data/spec/unit/hash_helper_spec.rb +44 -0
- data/spec/unit/migration_spec.rb +144 -0
- data/spec/unit/model_spec.rb +315 -0
- data/spec/unit/validations_spec.rb +182 -0
- data/tasks/bigrecord_tasks.rake +47 -0
- data/tasks/data_store.rb +46 -0
- data/tasks/gem.rb +22 -0
- data/tasks/rdoc.rb +8 -0
- data/tasks/spec.rb +34 -0
- metadata +189 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
module BigRecord
|
|
2
|
+
module BrAssociations
|
|
3
|
+
module CachedItemProxy #:nodoc:
|
|
4
|
+
|
|
5
|
+
CACHE_ATTRIBUTE = "attribute:associations_cache"
|
|
6
|
+
|
|
7
|
+
attr_reader :reflection
|
|
8
|
+
alias_method :proxy_respond_to?, :respond_to?
|
|
9
|
+
alias_method :proxy_extend, :extend
|
|
10
|
+
# delegate :to_param, :to => :proxy_target
|
|
11
|
+
# instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
|
|
12
|
+
|
|
13
|
+
def proxy_cache
|
|
14
|
+
@owner[CACHE_ATTRIBUTE] ||= {}
|
|
15
|
+
@owner[CACHE_ATTRIBUTE]["#{@reflection.klass.name}:#{id}"] ||= {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def proxy_owner
|
|
19
|
+
@owner
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def proxy_reflection
|
|
23
|
+
@reflection
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def proxy_target
|
|
27
|
+
@target
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def respond_to?(symbol, include_priv = false)
|
|
31
|
+
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Explicitly proxy === because the instance method removal above
|
|
35
|
+
# doesn't catch it.
|
|
36
|
+
def ===(other)
|
|
37
|
+
load_target
|
|
38
|
+
other === @target
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def aliased_table_name
|
|
42
|
+
@reflection.klass.table_name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset
|
|
46
|
+
@loaded = false
|
|
47
|
+
@target = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reload
|
|
51
|
+
reset
|
|
52
|
+
load_target
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def loaded?
|
|
56
|
+
@loaded
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def loaded
|
|
60
|
+
@loaded = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def target
|
|
64
|
+
@target
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def target=(target)
|
|
68
|
+
@target = target
|
|
69
|
+
loaded
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# # Returns the contents of the record as a nicely formatted string.
|
|
73
|
+
# def inspect
|
|
74
|
+
# if loaded?
|
|
75
|
+
# @target.inspect
|
|
76
|
+
# else
|
|
77
|
+
# attributes_as_nice_string = @reflection.options[:cache].collect { |name|
|
|
78
|
+
# column = @owner.column_for_attribute("attribute:#{name}")
|
|
79
|
+
# "#{name}: #{column.type_cast(proxy_cache[name])}" if column
|
|
80
|
+
# }.compact.join(", ")
|
|
81
|
+
# "#<Cached#{@reflection.klass} #{attributes_as_nice_string}>"
|
|
82
|
+
# end
|
|
83
|
+
# end
|
|
84
|
+
|
|
85
|
+
# def is_a?(klass)
|
|
86
|
+
# @reflection.klass <= klass
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# def kind_of?(klass)
|
|
90
|
+
# @reflection.klass <= klass
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
# def to_param
|
|
94
|
+
# @id
|
|
95
|
+
# end
|
|
96
|
+
|
|
97
|
+
# protected
|
|
98
|
+
# def dependent?
|
|
99
|
+
# @reflection.options[:dependent] || false
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# def quoted_record_ids(records)
|
|
103
|
+
# records.map { |record| record.quoted_id }.join(',')
|
|
104
|
+
# end
|
|
105
|
+
#
|
|
106
|
+
## def interpolate_sql_options!(options, *keys)
|
|
107
|
+
## keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
|
|
108
|
+
## end
|
|
109
|
+
##
|
|
110
|
+
## def interpolate_sql(sql, record = nil)
|
|
111
|
+
## @owner.send(:interpolate_sql, sql, record)
|
|
112
|
+
## end
|
|
113
|
+
##
|
|
114
|
+
## def sanitize_sql(sql)
|
|
115
|
+
## @reflection.klass.send(:sanitize_sql, sql)
|
|
116
|
+
## end
|
|
117
|
+
#
|
|
118
|
+
# def extract_options_from_args!(args)
|
|
119
|
+
# @owner.send(:extract_options_from_args!, args)
|
|
120
|
+
# end
|
|
121
|
+
#
|
|
122
|
+
# def set_belongs_to_association_for(record)
|
|
123
|
+
# if @reflection.options[:as]
|
|
124
|
+
# record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
|
125
|
+
# record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
|
126
|
+
# else
|
|
127
|
+
# record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
|
128
|
+
# end
|
|
129
|
+
# end
|
|
130
|
+
#
|
|
131
|
+
# def merge_options_from_reflection!(options)
|
|
132
|
+
# options.reverse_merge!(
|
|
133
|
+
# :group => @reflection.options[:group],
|
|
134
|
+
# :limit => @reflection.options[:limit],
|
|
135
|
+
# :offset => @reflection.options[:offset],
|
|
136
|
+
# :joins => @reflection.options[:joins],
|
|
137
|
+
# :include => @reflection.options[:include],
|
|
138
|
+
# :select => @reflection.options[:select]
|
|
139
|
+
# )
|
|
140
|
+
# end
|
|
141
|
+
|
|
142
|
+
# private
|
|
143
|
+
# def method_missing(method_id, *args, &block)
|
|
144
|
+
# if !loaded? and @reflection.options[:cache].include?(method_id)
|
|
145
|
+
# # FIXME: shouldn't be hard coded
|
|
146
|
+
# column = @owner.column_for_attribute("attribute:#{method_id}")
|
|
147
|
+
# if column
|
|
148
|
+
# if proxy_cache.has_key?(method_id)
|
|
149
|
+
# column.type_cast(proxy_cache[method_id])
|
|
150
|
+
# elsif load_target
|
|
151
|
+
# proxy_cache[method_id] = @target.send(method_id, *args, &block)
|
|
152
|
+
# end
|
|
153
|
+
# else
|
|
154
|
+
# @target.send(method_id, *args, &block)
|
|
155
|
+
# end
|
|
156
|
+
# elsif load_target
|
|
157
|
+
# value = @target.send(method_id, *args, &block)
|
|
158
|
+
# proxy_cache[method_id] = value if @reflection.options[:cache].include?(method_id)
|
|
159
|
+
# value
|
|
160
|
+
# end
|
|
161
|
+
# end
|
|
162
|
+
|
|
163
|
+
def load_target
|
|
164
|
+
return nil unless defined?(@loaded)
|
|
165
|
+
|
|
166
|
+
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
|
167
|
+
@target = find_target
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
@loaded = true
|
|
171
|
+
@target
|
|
172
|
+
rescue BigRecord::RecordNotFound
|
|
173
|
+
reset
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def find_target
|
|
177
|
+
@reflection.klass.find(self.id)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# # Can be overwritten by associations that might have the foreign key available for an association without
|
|
181
|
+
# # having the object itself (and still being a new record). Currently, only belongs_to present this scenario.
|
|
182
|
+
# def foreign_key_present
|
|
183
|
+
# false
|
|
184
|
+
# end
|
|
185
|
+
#
|
|
186
|
+
# def raise_on_type_mismatch(record)
|
|
187
|
+
# unless record.is_a?(@reflection.klass)
|
|
188
|
+
# raise BigRecordRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
|
|
189
|
+
# end
|
|
190
|
+
# end
|
|
191
|
+
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module BigRecord
|
|
2
|
+
module BrAssociations
|
|
3
|
+
class CachedItemProxyFactory
|
|
4
|
+
|
|
5
|
+
include Singleton
|
|
6
|
+
|
|
7
|
+
def create(id, owner, reflection)
|
|
8
|
+
cache = owner[CachedItemProxy::CACHE_ATTRIBUTE]
|
|
9
|
+
cached_attributes = cache["#{reflection.klass.name}:#{id}"] if cache
|
|
10
|
+
cached_attributes ||= {}
|
|
11
|
+
cached_attributes["id"] = id
|
|
12
|
+
proxy = reflection.klass.instantiate(cached_attributes)
|
|
13
|
+
proxy.extend CachedItemProxy
|
|
14
|
+
proxy.instance_variable_set(:@owner, owner)
|
|
15
|
+
proxy.instance_variable_set(:@reflection, reflection)
|
|
16
|
+
proxy.reset
|
|
17
|
+
|
|
18
|
+
# Overload the cached methods
|
|
19
|
+
reflection.options[:cache].each do |attribute_name|
|
|
20
|
+
eval "def proxy.#{attribute_name}\n"+
|
|
21
|
+
" proxy_cache[\"#{attribute_name}\"] ||= super\n"+
|
|
22
|
+
"end"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
proxy
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# def extended_class(reflection)
|
|
29
|
+
# @extended_classes ||= {}
|
|
30
|
+
# @extended_classes[reflection.klass.name] ||= create_extended_class(reflection)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# def create_extended_class(reflection)
|
|
34
|
+
# extended_class = Class.new(reflection.klass)
|
|
35
|
+
# extended_class.class_eval do
|
|
36
|
+
# include CachedItemProxy
|
|
37
|
+
#
|
|
38
|
+
# attr_reader :reflection
|
|
39
|
+
# alias_method :proxy_respond_to?, :respond_to?
|
|
40
|
+
# alias_method :proxy_extend, :extend
|
|
41
|
+
## delegate :to_param, :to => :proxy_target
|
|
42
|
+
#
|
|
43
|
+
# # Overload the methods
|
|
44
|
+
# instance_methods.each do |m|
|
|
45
|
+
# if reflection.options[:cache].include?(m.to_sym)
|
|
46
|
+
# define_method m do
|
|
47
|
+
# proxy_cache[m.to_s] ||= super
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# def class
|
|
53
|
+
# reflection.klass
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# end
|
|
57
|
+
# extended_class
|
|
58
|
+
# end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
module BigRecord
|
|
2
|
+
module BrAssociations
|
|
3
|
+
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
|
4
|
+
def initialize(owner, reflection)
|
|
5
|
+
super
|
|
6
|
+
construct_sql
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def build(attributes = {})
|
|
10
|
+
load_target
|
|
11
|
+
build_record(attributes)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create(attributes = {})
|
|
15
|
+
create_record(attributes) { |record| insert_record(record) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create!(attributes = {})
|
|
19
|
+
create_record(attributes) { |record| insert_record(record, true) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def find_first
|
|
23
|
+
load_target.first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def find(*args)
|
|
27
|
+
options = args.extract_options!
|
|
28
|
+
|
|
29
|
+
# If using a custom finder_sql, scan the entire collection.
|
|
30
|
+
if @reflection.options[:finder_sql]
|
|
31
|
+
expects_array = args.first.kind_of?(Array)
|
|
32
|
+
ids = args.flatten.compact.uniq
|
|
33
|
+
|
|
34
|
+
if ids.size == 1
|
|
35
|
+
id = ids.first.to_i
|
|
36
|
+
record = load_target.detect { |record| id == record.id }
|
|
37
|
+
expects_array ? [record] : record
|
|
38
|
+
else
|
|
39
|
+
load_target.select { |record| ids.include?(record.id) }
|
|
40
|
+
end
|
|
41
|
+
else
|
|
42
|
+
# Generate the sql query. The join table has to be in mysql.
|
|
43
|
+
conditions = "#{@finder_sql}"
|
|
44
|
+
|
|
45
|
+
if sanitized_conditions = ActiveRecord::Base.send(:sanitize_sql, options[:conditions])
|
|
46
|
+
conditions << " AND (#{sanitized_conditions})"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if options[:order] && @reflection.options[:order]
|
|
50
|
+
order = "#{options[:order]}, #{@reflection.options[:order]}"
|
|
51
|
+
elsif @reflection.options[:order]
|
|
52
|
+
order = @reflection.options[:order]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
query = "SELECT #{@reflection.association_foreign_key} FROM #{@reflection.options[:join_table]} WHERE #{conditions} #{order};"
|
|
56
|
+
|
|
57
|
+
# Execute the query
|
|
58
|
+
ids = []
|
|
59
|
+
ActiveRecord::Base.connection.execute(query).each do |result|
|
|
60
|
+
ids << result.first
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Find the big_record entries by id. Find them one at a time because duplicate entries must appear in
|
|
64
|
+
# the result set if it's the case. The default find by ids in big_record does the same thing anyway...
|
|
65
|
+
ids.collect{|id| @reflection.klass.find(id)}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
protected
|
|
70
|
+
def count_records
|
|
71
|
+
load_target.size
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def insert_record(record, force=true)
|
|
75
|
+
if record.new_record?
|
|
76
|
+
if force
|
|
77
|
+
record.save!
|
|
78
|
+
else
|
|
79
|
+
return false unless record.save
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if @reflection.options[:insert_sql]
|
|
84
|
+
ar_connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
|
|
85
|
+
else
|
|
86
|
+
columns = ar_connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
|
87
|
+
|
|
88
|
+
attributes = columns.inject({}) do |attributes, column|
|
|
89
|
+
case column.name
|
|
90
|
+
when @reflection.primary_key_name
|
|
91
|
+
attributes[column.name] = @owner.quoted_id
|
|
92
|
+
when @reflection.association_foreign_key
|
|
93
|
+
attributes[column.name] = record.quoted_id
|
|
94
|
+
else
|
|
95
|
+
if record.attributes.has_key?(column.name)
|
|
96
|
+
value = @owner.send(:quote_value, record[column.name], column)
|
|
97
|
+
attributes[column.name] = value unless value.nil?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
attributes
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
sql =
|
|
104
|
+
"INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
|
105
|
+
"VALUES (#{attributes.values.join(', ')})"
|
|
106
|
+
|
|
107
|
+
ar_connection.execute(sql)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
return true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def delete_records(records)
|
|
114
|
+
if sql = @reflection.options[:delete_sql]
|
|
115
|
+
records.each { |record| ar_connection.execute(interpolate_sql(sql, record)) }
|
|
116
|
+
else
|
|
117
|
+
ids = quoted_record_ids(records)
|
|
118
|
+
sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
|
119
|
+
ar_connection.execute(sql)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def construct_sql
|
|
124
|
+
# interpolate_sql_options!(@reflection.options, :finder_sql)
|
|
125
|
+
|
|
126
|
+
if @reflection.options[:finder_sql]
|
|
127
|
+
@finder_sql = @reflection.options[:finder_sql]
|
|
128
|
+
else
|
|
129
|
+
@finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
|
|
130
|
+
# @finder_sql << " AND (#{conditions})" if conditions
|
|
131
|
+
end
|
|
132
|
+
# @join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def construct_scope
|
|
136
|
+
{ :find => { :conditions => @finder_sql,
|
|
137
|
+
# :joins => @join_sql,
|
|
138
|
+
:readonly => false,
|
|
139
|
+
:order => @reflection.options[:order],
|
|
140
|
+
:limit => @reflection.options[:limit] } }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
|
|
144
|
+
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
|
|
145
|
+
# an id column. This will then overwrite the id column of the records coming back.
|
|
146
|
+
def finding_with_ambiguous_select?(select_clause)
|
|
147
|
+
!select_clause && ar_connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
def create_record(attributes)
|
|
152
|
+
# Can't use Base.create because the foreign key may be a protected attribute.
|
|
153
|
+
ensure_owner_is_not_new
|
|
154
|
+
if attributes.is_a?(Array)
|
|
155
|
+
attributes.collect { |attr| create(attr) }
|
|
156
|
+
else
|
|
157
|
+
record = build(attributes)
|
|
158
|
+
yield(record)
|
|
159
|
+
record
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def ar_connection
|
|
164
|
+
(@owner.class < ActiveRecord::Base) ? @owner.connection : ActiveRecord::Base.connection
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module BigRecord
|
|
2
|
+
module BrAssociations
|
|
3
|
+
class HasOneAssociation < BelongsToAssociation #:nodoc:
|
|
4
|
+
def initialize(owner, reflection)
|
|
5
|
+
super
|
|
6
|
+
construct_sql
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(attributes = {}, replace_existing = true)
|
|
10
|
+
record = build(attributes, replace_existing)
|
|
11
|
+
record.save
|
|
12
|
+
record
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def build(attributes = {}, replace_existing = true)
|
|
16
|
+
record = @reflection.klass.new(attributes)
|
|
17
|
+
|
|
18
|
+
if replace_existing
|
|
19
|
+
replace(record, true)
|
|
20
|
+
else
|
|
21
|
+
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
|
22
|
+
self.target = record
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
record
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def replace(obj, dont_save = false)
|
|
29
|
+
load_target
|
|
30
|
+
|
|
31
|
+
unless @target.nil?
|
|
32
|
+
if dependent? && !dont_save && @target != obj
|
|
33
|
+
@target.destroy unless @target.new_record?
|
|
34
|
+
@owner.clear_association_cache
|
|
35
|
+
else
|
|
36
|
+
@target[@reflection.primary_key_name] = nil
|
|
37
|
+
@target.save unless @owner.new_record? || @target.new_record?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if obj.nil?
|
|
42
|
+
@target = nil
|
|
43
|
+
else
|
|
44
|
+
raise_on_type_mismatch(obj)
|
|
45
|
+
set_belongs_to_association_for(obj)
|
|
46
|
+
@target = (AssociationProxy === obj ? obj.target : obj)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@loaded = true
|
|
50
|
+
|
|
51
|
+
unless @owner.new_record? or obj.nil? or dont_save
|
|
52
|
+
return (obj.save ? self : false)
|
|
53
|
+
else
|
|
54
|
+
return (obj.nil? ? nil : self)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
def find_target
|
|
60
|
+
@reflection.klass.find(:first,
|
|
61
|
+
:conditions => @finder_sql,
|
|
62
|
+
:order => @reflection.options[:order],
|
|
63
|
+
:include => @reflection.options[:include]
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# def construct_sql
|
|
68
|
+
# case
|
|
69
|
+
# when @reflection.options[:as]
|
|
70
|
+
# @finder_sql =
|
|
71
|
+
# "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
|
72
|
+
# "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
|
73
|
+
# else
|
|
74
|
+
# @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
|
75
|
+
# end
|
|
76
|
+
# @finder_sql << " AND (#{conditions})" if conditions
|
|
77
|
+
# end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|