====== Many-to-many without habtm ====== Has_and_belongs_to_many (habtm for short) is handy, it even creates a //join// model and installer by itself. But what if you want to have extra columns in the //join// table with information on the relation between the two tables? Not possible in habtm, and there is no //[[http://wiki.rubyonrails.org/rails/pages/Beginner+Howto+on+has_many+%3Athrough|has_many :through]]// like in Rails, but there is another way! ====== Database ====== Let's say we have the string instruments shop from the [[how-to-implement-many-to-many-relationships|habtm example]]. Because sales aren't going so well the owner decided to rent his instruments to clients. So, if you your violin keeps sounding awful, even after years of practice, you could rent that Stradivarius! For a weekend or so. Apart from the //products// table, we need two more: one with the clients and one with the rentals. Generate the models: script/generate model product script/generate model client script/generate model rental And we could combine the installers into one: createTable('products', " id, name varchar(100), description varchar(1000), selling_price numeric(12,2), rental_price_per_day numeric(6,2) "); $this->createTable('clients', " id, first_name varchar(100), last_name varchar(100), address varchar(1000), telephone numeric(12,2) "); $this->createTable('rentals', " id, product_id, client_id, pickup_date date, return_date date "); } } ?> So: * Every //product// can have //rentals//, each of them to a //client// * Every //client// can also have //rentals//, each of them being a //product// A habtm would suffice, were it not that we keep extra information in the join table: //pickup_date// and //return_date//, enabling the owner to keep track of when instrument should return to the shop. Plus, a day count between the two dates would enable a calculation of the total rental price for an instrument. ====== Models ====== After some scaffolding we can define the relationships between the models. class Product extends ActiveRecord { var $has_many = array('rentals' => array('dependent' => 'destroy')); } class Client extends ActiveRecord { var $has_many = array('rentals' => array('dependent' => 'destroy')); } class Rental extends ActiveRecord { var $belongs_to = array('product','client'); } (The //dependent => destroy// option makes sure associated rentals are deleted together with a client or product.) ====== Controllers ====== Now, in both the //product_controller// and to //client_controller// you should of course load the associated models. In //product_controller//: var $models = array('product','rental'); In //client_controller//: var $models = array('client','rental'); \\ Now we can load the rentals in the client controller using: $client->rental->load(); ..when showing or editing a single client record.\\ (for a listing we would include //array('include' => 'rental')// as option in the finder)\\ And in a view we can loop through the rental columns using: \\ .. but which instruments did the client take home?\\ Well, we'll have to make a special finder for that in the model. ====== A custom finder ===== In the //client// model we first make a method that can load the product details for each rental: function loadProductForEachRental() { foreach ($this->Rentals as &$Rental){ $Rental->Product->load(); } } And then we can make a custom finder which makes use of method above: function findClientWithRentalsAndBelongingProducts($id) { $Client = $this->find($id,array('include'=>'rental')); $Client->loadProductForEachRental(); return $Client; } So in the //edit// or //show// function in the //client_controller// we can now call: $this->Client = $this-> Client->findClientWithRentalsAndBelongingProducts('id',@$this->params['id']); Which will make this possible in the view: