I have an interesting Active Record problem and I\'m not quite sure what the cleanest solution is. The legacy database that I am integrating with has a strange wrinkle in its sc
DB table partitioning is pretty common practice really. I'd be surprised if someone hasn't done this before. How about ActsAsPartitionable? http://revolutiononrails.blogspot.com/2007/04/plugin-release-actsaspartitionable.html
Another possibility: can your DBMS pretend that the partitions are one big table? I think MySQL supports this.
Assumption 1: you know what type of car you're looking at, so you can tell if it's a ford or a dodge.
Lets put the car make in an attribute called (of all things) make. You should normalize this later, but for now lets keep it simple.
CREATE TABLE cars (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255),
'make' varchar(255),
#ect
)
class Wheel < ActiveRecord::Base
def find_by_make(iMake)
select("wheels_for_#{iMake}.*").from("wheels_for_#{iMake}");
end
#...
end
You could add some protection in there to validate and downcase your iMake. You could also do something to ensure your table exists.
Now writing to the table.. I'm not sure how that would work. I've only ever read from it. Perhaps its something simple too.
Why not simply put all the wheels in one table and use a standard :has_many? You can do it in a migration:
Here's a way you can do it. The basics (before the 70 lines of code) are:
Let me know if you have any questions
#!/usr/bin/env ruby
%w|rubygems active_record irb|.each {|lib| require lib}
ActiveSupport::Inflector.inflections.singular("toyota", "toyota")
CAR_TYPES = %w|ford buick toyota|
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database => ":memory:"
)
ActiveRecord::Schema.define do
create_table :cars do |t|
t.string :name
end
create_table :car_to_wheel_table_map, :id => false do |t|
t.integer :car_id
t.string :wheel_table
end
CAR_TYPES.each do |car_type|
create_table "wheels_for_#{car_type.pluralize}" do |t|
t.integer :car_id
t.string :color
end
end
end
CAR_TYPES.each do |car_type|
eval <<-END
class #{car_type.classify}Wheel < ActiveRecord::Base
set_table_name "wheels_for_#{car_type.pluralize}"
belongs_to :car
end
END
end
class Car < ActiveRecord::Base
has_one :car_wheel_map
CAR_TYPES.each do |car_type|
has_many "#{car_type}_wheels"
end
delegate :wheel_table, :to => :car_wheel_map
def wheels
send("#{wheel_table}_wheels")
end
end
class CarWheelMap < ActiveRecord::Base
set_table_name "car_to_wheel_table_map"
belongs_to :car
end
rav4 = Car.create(:name => "Rav4")
rav4.create_car_wheel_map(:wheel_table => "toyota")
rav4.wheels.create(:color => "red")
fiesta = Car.create(:name => "Fiesta")
fiesta.create_car_wheel_map(:wheel_table => "ford")
fiesta.wheels.create(:color => "green")
IRB.start if __FILE__ == $0
I would do this association with custom function in model:
has_one :cat_to_wheel_table_map
def wheels
Wheel.find_by_sql("SELECT * FROM #{cat_to_wheel_table_map.wheel_table} WHERE car_id == #{id}")
end
Maybe you can do it using association with :finder_sql, but I'm not sure how to pass arguments to it. I used model Wheel which you have to define if you want your data to be mapped by ActiveRecord. Probably you can make this model from one of your existing tables with wheels.
And I didn't test it ;).
How about this instead? (here's the gist: http://gist.github.com/111041)
#!/usr/bin/env ruby
%w|rubygems active_record irb|.each {|lib| require lib}
ActiveSupport::Inflector.inflections.singular("toyota", "toyota")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database => ":memory:"
)
ActiveRecord::Schema.define do
create_table :cars do |t|
t.string :name
end
create_table :car_to_wheel_table_map, :id => false do |t|
t.integer :car_id
t.string :wheel_table
end
create_table :wheels_for_fords do |t|
t.integer :car_id
t.string :color
end
create_table :wheels_for_toyotas do |t|
t.integer :car_id
t.string :color
end
end
class Wheel < ActiveRecord::Base
set_table_name nil
belongs_to :car
end
class CarWheelMap < ActiveRecord::Base
set_table_name "car_to_wheel_table_map"
belongs_to :car
end
class Car < ActiveRecord::Base
has_one :car_wheel_map
delegate :wheel_table, :to => :car_wheel_map
def wheels
@wheels ||= begin
the_klass = "#{wheel_table.classify}Wheel"
eval <<-END
class #{the_klass} < ActiveRecord::Base
set_table_name "wheels_for_#{wheel_table.pluralize}"
belongs_to :car
end
END
self.class.send(:has_many, "#{wheel_table}_wheels")
send "#{wheel_table}_wheels"
end
end
end
rav4 = Car.create(:name => "Rav4")
rav4.create_car_wheel_map(:wheel_table => "toyota")
fiesta = Car.create(:name => "Fiesta")
fiesta.create_car_wheel_map(:wheel_table => "ford")
rav4.wheels.create(:color => "red")
fiesta.wheels.create(:color => "green")
# IRB.start if __FILE__ == $0