Recipe 6.3. Modeling Relationships RESTfully with Join Models


Problem

Contributed by: Diego Scataglini

You want to create a REST API that allows many-to-many relationships between models to be created while remaining true to the REST approach. For example, you want to expose the Subscription relationship between a User and a Magazine model in your REST API.

Solution

Start off by creating an empty Rails application and configuring the database.yml file to access your database. With your basic setup complete, create the core models for your application:

$ ruby script/generate scaffold_resource User $ ruby script/generate scaffold_resource Magazine             

Because you're going to create a full-fledged model to represent the relationship between User and Magazine, run a scaffold_resourc e generator for the Subscription join model:

$ ruby script/generate scaffold_resource Subscription             

Next up, edit the code for the models to establish their relationships. Note how the :class_name parameter allows you to use the most natural name for the relationship, regardless of the actual name of the model class.

app/models/user.rb:

class User < ActiveRecord::Base   has_many :subscriptions   has_many :magazines, :through => :subscriptions end

app/models/magazine.rb:

class Magazine < ActiveRecord::Base   has_many :subscriptions   has_many :subscribers, :through => :subscriptions end

app/models/subscription.rb:

class Subscription < ActiveRecord::Base   belongs_to :subscriber,      :class_name => "User", :foreign_key => "user_id"    belongs_to :magazine end

Next, you'll need to define the database schema in your three migration files and create some test data for users and magazines. You'll create the subscriptions later using the REST API.

db/migrate/001_create_users.rb:

class CreateUsers < ActiveRecord::Migration   def self.up     create_table :users do |t|       t.column :login, :string       t.column :email, :string     end     User.create(:login => "diego")     User.create(:login => "rob")     User.create(:login => "chris")   end   def self.down     drop_table :users   end end

db/migrate/002_create_magazines.rb

:

class CreateMagazines < ActiveRecord::Migration   def self.up     create_table :magazines do |t|       t.column :title, :string     end     Magazine.create(:title => "Rails Mag")     Magazine.create(:title => "Ruby Red Babes")   end   def self.down     drop_table :magazines   end end

db/migrate/003_create_subscriptions.rb

:

class CreateSubscriptions < ActiveRecord::Migration   def self.up     create_table :subscriptions do |t|       t.column :user_id, :integer       t.column :magazine_id, :integer       t.column :subscription_type, :string     end   end   def self.down     drop_table :subscriptions   end end

All that remains is to migrate your database to create the tables and data. Run the following command now:

$ rake db:migrate             

Your API is now ready. Start the development server:

$ ruby script/server -d              

To exercise your shiny new API and create some magazine subscriptions, install the as yet unreleased Active Resource library. Execute the following shell command:

$ svn co http://dev.rubyonrails.org/svn/rails/trunk/activeresource vendor/   rails/activeresource             

Make the following modification to your environment:

config/environment.rb:

require "#{RAILS_ROOT}/vendor/rails/activeresource/lib/active_resource.rb"

Now, simply open the Rails development console, and mimic the session below:

>> class Subscription < ActiveResource::Base >> self.site = 'http://localhost:3000' >> end => "subscription" >> subscription = Subscription.new(:user_id => 1, :magazine_id => 2,  :subscription_type => "monthly") => #<Subscription:0x5843820 @attributes={"magazine_id"=>2, "user_id"=>1,  "subscription_type"=>"monthly"}, @prefix_options={}> >> subscription.save => true >> Subscription.find(1) => #<Subscription:0x5838aec @attributes={"id"=>"1", "magazine_id"=>"2",  "user_id"=>"1", "subscription_type"=>"monthly"}>

Discussion

The reason you didn't use a "has and belongs to many" relationship is that you cannot refer to it as a model object. This turns out to be an important limitation when the relationship itself has important characteristics you'd like to track. In the subscription scenario, the length of the subscription is an important piece of data. Another typical case is a loan, which is essentially a relationship between lender and borrower, but has many important aspects of its own such as loan type, loan status (pending, approved, denied), interest rate, term length, and closing date. For simpler relationships, where nothing interesting is happening at the intersection of the two models, a standard has_and_belongs_to_many relationship may suffice:

class User < ActiveRecord::Base   has_and_belongs_to_many :magazines end class Magazine < ActiveRecord::Base   has_and_belongs_to_many :users end

Events and state changes such as subscription cancellations and suspensions can also be tricky to model. Traditionally, you might think of a cancellation as an action or verb. The REST philosophy, however, views a cancellation as a simple change in the state of the subscription from active to cancelled. In short, there are many different approaches for the design and modeling of these types of applications. The REST approach may require you to adopt a new way of looking at your application, but Rails provides great tools and architectural guidance to make the transition as painless as possible.




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net