I have 2 types of users, one is called list_owner and one is called subscriber. These are both users made with devise. Subscribers should be able to follow the list_owner. I have another model called Relationship to keep track who the subscriber follows.
I used http://ruby.railstutorial.org/ mostly as an example.
These are the relations
class Relationship < ActiveRecord::Base
attr_accessible :list_owner_id, :subscriber_id
belongs_to :list_owner
belongs_to :subscriber
end
class ListOwner < ActiveRecord::Base
has_many :messages
has_many :relationships, :dependent => :destroy, :foreign_key => "list_owner_id"
end
class Subscriber < ActiveRecord::Base
has_many :relationships, :foreign_key => "subscriber_id",
:dependent => :destroy
def follow!(list_owner)
puts "-----------------------"
relationships.create!(:subscriber_id => subscriber.id, :list_owner_id => list_owner.id)
end
end
I made a list with list owners with a button where the subscriber can subscribe to.
<%= form_for current_subscriber.relationships.build(:list_owner_id => l.id, :subscriber_id => #subscriber.id),
:remote => true do |f| %>
<%= f.hidden_field :list_owner_id %>
<%= f.hidden_field :subscriber_id %>
<%= f.submit "Subscribe", :class => "button btn" %>
<% end %>
class RelationshipsController < ApplicationController
def create
puts "---------------------relationships CREATE---------------------"
#list_owner = ListOwner.find(params[:relationship][:list_owner_id])
current_subscriber.follow!(#list_owner)
redirect_to root_path
end
end
This is the error i get. What am i doing wrong?
Started POST "/relationships" for 127.0.0.1 at Wed Sep 07 15:37:17 +0200 2011
Processing by RelationshipsController#create as JS
Parameters: {"commit"=>"Subscribe", "relationship"=>{"list_owner_id"=>"2", "subscriber_id"=>"1"}, "authenticity_token"=>"m5plsJLdzuwNt1yKfDJFKD28GcR138V+pezbfbECCPk=", "utf8"=>"✓", "_"=>""}
ListOwner Load (0.2ms) SELECT "list_owners".* FROM "list_owners" WHERE "list_owners"."id" = 2 LIMIT 1
Subscriber Load (0.2ms) SELECT "subscribers".* FROM "subscribers" WHERE "subscribers"."id" = 1 LIMIT 1
Completed 500 Internal Server Error in 34ms
NameError (undefined local variable or method `subscriber' for #<Subscriber:0x102f140c0>):
app/models/subscriber.rb:21:in `follow!'
app/controllers/relationships_controller.rb:8:in `create'
There's no such 'subscriber' object defined, but you don't need one, since you're calling the create!() method on the association collection, which will pre-populate the parent object. That is, calling subscriber.relationships.create! will set subscriber_id = subscriber.id.
def follow!(list_owner)
relationships.create!(:list_owner_id => list_owner.id)
end
Related
This error has been troubling me for weeks. When I try to build an Item from a specified User and List, the Item is created, but the association, Wish is not.
If i try to do #item.lists.first.name it returns an error:
undefined method 'name' for nil:NilClass
I'm very new to rails, so I'm sure there is something that I have missed or misunderstood. Any help is thus much appreciated!
I have four models:
class User < ActiveRecord::Base
has_many :lists, dependent: :destroy
has_many :wishes, through: :lists
has_many :items, through: :wishes
class List < ActiveRecord::Base
belongs_to :user
has_many :wishes, dependent: :destroy
has_many :items, through: :wishes
class Wish < ActiveRecord::Base
belongs_to :list
belongs_to :item
class Item < ActiveRecord::Base
has_many :wishes
has_many :lists, through: :wishes
I want to create a new Item from lists_controller.rb show action:
class ListsController < ApplicationController
def show
#user = User.find(params[:user_id])
#list = #user.lists.find(params[:id])
#item = #list.items.build if current_user?(#user)
#items = #list.items
end
class ItemsController < ApplicationController
def create
#list = current_user.lists.find(params[:list_id])
#item = #list.items.build(params[:item])
if #item.save
flash[:success] = "Item created!"
redirect_to #item
else
render 'new'
end
end
My route file looks like this:
Wishlist::Application.routes.draw do
resources :users do
resources :lists, only: [:show, :create, :destroy]
end
resources :items, only: [:show, :new, :create, :destroy]
The form lists/show.html.erb:
<div class="modal-body">
<%= form_for(#item, html: { multipart: true }) do |f| %>
<div class="field">
<%= render 'items/fields', f: f %>
<%= hidden_field_tag :list_id, #list.id %>
</div>
</div>
and items/fields:
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :image %>
<%= f.file_field :image %>
<%= f.label :remote_image_url, "or image URL" %>
<%= f.text_field :remote_image_url %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :link %>
<%= f.text_field :link %>
Update
From the log:
Processing by ItemsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"pcDVdaDzZz4M17Kwjx8mKw6tTF9MjUvx1woTzaKRWJY=", "item"=>{"image"=>#<ActionDispatch::Http::UploadedFile:0x007fecdd1fd890 #original_filename="profile.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"item[image]\"; filename=\"profile.jpg\"\r\nContent-Type: image/jpeg\r\n", #tempfile=#<File:/var/folders/6y/j8zfcgmd02x5s439c0np8fjh0000gn/T/RackMultipart20130216-8413-3vzjuj>>, "remote_image_url"=>"", "title"=>"YES", "link"=>"SDFs"}, "list_id"=>"1", "commit"=>"Add"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '5AXS8-7-YRyLyGDKYHIZRg' LIMIT 1
List Load (0.2ms) SELECT "lists".* FROM "lists" WHERE "lists"."user_id" = 1 AND "lists"."id" = ? LIMIT 1 [["id", "1"]]
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "items" ("created_at", "image", "link", "title", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Sat, 16 Feb 2013 20:57:10 UTC +00:00], ["image", "profile.jpg"], ["link", "SDFs"], ["title", "YES"], ["updated_at", Sat, 16 Feb 2013 20:57:10 UTC +00:00]]
(2.7ms) commit transaction
Redirected to http://localhost:3000/items/1
Completed 302 Found in 830ms (ActiveRecord: 4.0ms)
This appears to be a known issue (but not really an issue) when using build on a has_many :through. You need to change the association for items on List to include inverse_of.
class List < ActiveRecord::Base
belongs_to :user
has_many :wishes, dependent: :destroy
has_many :items, through: :wishes, inverse_of: :wishes
end
It should then build the connecting Wish object for you properly.
See this relevant issue for more information: https://github.com/rails/rails/pull/7661#issuecomment-8614206
Let me try to summarize first: you have users, which have lists, which contain wishes, which are related to one particular item.
So let's have a look at the following line of code first:
#item.lists.first.name
So what happens is, that rails takes the item, goes through the wishes to all lists that contain this item, takes the first list and looks for the lists name. The problem you see occurs when the item is not related to any lists through wishes (e.g. because your create method for items does not connect new items to wishes and therefore the connection to the list is missing as well): So the item has no relation to a list and the result for #item.lists is an empty list. And well, the first element of an empty list is nothing (nil) and of course nil has no name.
The result is, what you see:
undefined method 'name' for nil:NilClass
One way to fix this, is to use try() which executes the related method only if it is available:
#item.lists.first.try(:name)
The other problem, you were talking about, is that no wish is generated, when you build an item from a user. I think you have to explicitly build the wish. I'm not 100% sure, but I think, declaring :through-relations in the model is only used for queries, not for generating/building.
Try something like the following in your items controller (The idea is to build the wish explicitly first, and after that build the item for the wish):
#wish = #list.wishes.build()
#wish.save
#item = #wish.item.build(params[:item])
After lots of googling, and trial and error, I found that I had to build the Wish explicitly. The create action ended up looking like this:
def create
#list = current_user.lists.find(params[:list_id])
#item = Item.create!(params[:item])
#item.wishes.build(list_id: #list.id)
if #item.save
flash[:success] = "Item created!"
redirect_to #item
else
render 'new'
end
end
I am getting this follow error after I added more relationships to my project. On localhost, the page displays perfectly fine. However, I get an error when trying to view it on heroku.
ActiveRecord::StatementInvalid (PG::Error: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
Here is my traveldeal controller:
def show
#traveldeal = #commentable = Traveldeal.find(params[:id])
#comments = Comment.all
#title = #traveldeal.title
#eventdeals = Eventdeal.tagged_with(#traveldeal.location_tag_list, :any => true, :order => 'RANDOM()', :limit => 3)
end
traveldeal/show.html
<% unless #eventdeals.blank? %>
<h1>Events in the area:</h1>
<% #eventdeals.each do |eventdeal| %>
<%= link_to eventdeal do %>
<!-- content -->
<% end %>
<% end %>
<% end %>
Heroku logs
SELECT DISTINCT eventdeals.* FROM "eventdeals" JOIN taggings event_taggings_e9f0e2e ON event_taggings_e9f0e2e.taggable_id = eventdeals.id AND event_taggings_e9f0e2e.taggable_type = 'Eventdeal' WHERE (event_taggings_e9f0e2e.tag_id = 1 OR event_taggings_e9f0e2e.tag_id = 3 OR event_taggings_e9f0e2e.tag_id = 4 OR event_taggings_e9f0e2e.tag_id = 5) ORDER BY RANDOM()):
This code was working so that I only displayed 3 random eventdeals that matched location (through act-as-taggable-on) with a traveldeal.
However, after I added a relationship (related to eventdeals but not traveldeals), I started getting the pg error.
Here are the relationships that was added:
trip.rb
class Trip < ActiveRecord::Base
has_many :eventdealtrips, :dependent => :destroy
has_many :eventdeals, :through => :eventdealtrips
end
eventdeal.rb
class Eventdeal < ActiveRecord::Base
has_many :eventdealtrips
has_many :trips, :through => :eventdealtrips, :dependent => :destroy
end
eventdealtrip.rb
class Eventdealtrip < ActiveRecord::Base
belongs_to :eventdeal
belongs_to :trip
end
Any advice on how I can still get a random array of 3 eventdeals?
Thanks.
I had this issue and solved it by running order at the end of my daisy chain.
I am trying to create a dashboard where users (User model) who has clicked "attending" (which is a flaggable) to an event (Event model) which is connected to a Collection (Collection model) will be able to see what are the Events they are going for.
My question however, is simply how to loop through all possible arrays in order for me to get all the associated IDs for the Events that the User has clicked "attending".
So for example, in my home page I want to display all possible events that the user is attending:
user_statistics.html.erb
<div class="span3 events">
<h3>Events</h3>
<% if #events.empty? %>
<p>You are currently not attending any events.</p>
<% else %>
<p>You are attending: <b><%= pluralize(#events.count, "event") %></b></p>
<p>Event 1: <%= #event1_name %> on Date: <%= #event1.date %> at Time:<%= #event1.time %></p>
<% end %>
</div>
pages_controller.rb
def home
#title = "Title"
#user = current_user
if current_user
#post = Post.new
#feed_items = current_user.feed
#user_following = #user.following
#user_followers = #user.followers
#events = #user.flaggings.with_flag(:attending)
#event1 = Event.find(#events[0].flaggable_id)
#event1_name = Collection.find(#event1.collection_id).name
end
end
I have set the #event1 array to 0 to access the first flag for 'attending', and then get the flaggable_id so I have the id to call up the Collection.
My issue is that if I have several events, how do I go about looping through to all the arrays to ensure I can pull out all the Collections?
For the first User who has clicked "attending" for 2 events, this is the data:
in IRB, for User.first who is attending 2 events
User Load (0.3ms) SELECT "users".* FROM "users" LIMIT 1
MakeFlaggable::Flagging Load (0.4ms) SELECT "flaggings".* FROM "flaggings" WHERE
"flaggings"."flagger_id" = 1 AND "flaggings"."flagger_type" = 'User' AND
"flaggings"."flag" = 'attending'
[#<MakeFlaggable::Flagging id: 16, flaggable_type: "Event", flaggable_id: 3,
flagger_type: "User", flagger_id: 1, flag: "attending", created_at: "2012-02-20 09:26:36",
updated_at: "2012-02-20 09:26:36">, #<MakeFlaggable::Flagging id: 18, flaggable_type:
"Event", flaggable_id: 4, flagger_type: "User", flagger_id: 1, flag: "attending",
created_at: "2012-02-20 10:38:00", updated_at: "2012-02-20 10:38:00">]
You can see that the user has flagged 'attending' for 2 events, which are stored in arrays. Hence if I have 2 events, I would ideally want to loop through such that I have 2 arrays.
I find it quite confusing to implement this... any help would be much appreciated!
user.rb partial code
Class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :user_bio,
:shop, :cover_photo, :avatar, :remote_image_url
has_secure_password
mount_uploader :cover_photo, ImageUploader
mount_uploader :avatar, ImageUploader
make_flagger
scope :shop, where(shop: true)
has_many :posts, dependent: :destroy
has_many :relationships, dependent: :destroy,
foreign_key: "follower_id"
has_many :reverse_relationships, dependent: :destroy,
foreign_key: "followed_id",
class_name: "Relationship"
has_many :following, through: :relationships, source: :followed
has_many :followers, through: :reverse_relationships, source: :follower
has_many :collections, dependent: :destroy
...
end
collections.rb partial code
class Collection < ActiveRecord::Base
attr_accessible :name, :description, :image, :remote_image_url
belongs_to :user
has_many :products, dependent: :destroy
has_many :events, dependent: :destroy
mount_uploader :image, ImageUploader
make_flaggable :like
...
end
events.rb partial code
class Event < ActiveRecord::Base
attr_accessible :date, :time, :description
belongs_to :collections
make_flaggable :attending
...
end
It's not 100% clear from your post, but it sounds like you need a named scope for Events where the attending flag is set and a has_many :through association to let the User have Events. With those two bits together, you could do something like:
User.first.events.attending
and AREL will take care of wiring that all up into a nice fast query for you.
Is flaggable a gem or have you written it as a polymorphic class?
This line is kind of nasty (sorry):
#events = #user.flaggings.with_flag(:attending)
The #events variable isn't holding events - it's holding a collection of flaggings. As a naming convention, this is obviously bad.
Also, this line is probably redundant if you've set up (or used a gem) for the flaggable polymorphic relationship:
#event1 = Event.find(#events[0].flaggable_id)
#This could be rewritten as:
#event1 = #events.first.flaggable
Or even nicer, combine the two previous lines into:
##events = #user.flaggings.with_flag(:attending)
##event1 = Event.find(#events[0].flaggable_id)
#becomes:
flaggings = #user.flaggings.with_flag(:attending)
#events = flaggings.map(&:flaggable)
#jxpx777 makes a very good point about named scope and hmt associations. This is probably the way forward. You could do something like this:
Class User < AR...
has_many :attendances, :class => 'Flagging', :conditions => {:flag => 'attending'} #You might need to add :type => "Event" if you use flaggings elsewhere...
has_many :events_attending, :class => 'Event', :through => :attendances, :source => :flaggable_id #You'll probably have to change a lot of these variable names - I can't see the rest of your source code...
That will then give you ability to just do:
#events = #user.events_attending.include(:collection)
Caveat - none of this code is tested - it's all off the top of my head, but it should point you in the right direction at least
Okay, I managed to solve my own problem albeit in a very dirty manner...
pages_controller.rb
def home
#title = "Simplifying and socializing online shopping - Ruuva"
#user = current_user
if current_user
#post = Post.new
#feed_items = current_user.feed
#user_following = #user.following
#user_followers = #user.followers
#events_attending = #user.flaggings.with_flag(:attending)
end
end
_user_statistics.html.erb partial
<h3>Events</h3>
<% if #events_attending.empty? %>
<p>You are currently not attending any events.</p>
<% else %>
<p>You are attending these events:</p>
<ol>
<% #events_attending.each do |e| %>
<% unless Event.find_by_id(e.flaggable_id).nil? %>
<li>
Collection: <%= link_to Collection.find(Event.find(e.flaggable_id).collection_id).name, event_path(e.flaggable_id) %>
<br>happening on: <%= Event.find(e.flaggable_id).date %> at <%= Event.find(e.flaggable_id).time %>
</li>
<% end %>
<% end %>
</ol>
<% end %>
I'm pretty sure the code is very bad, and I DO used several 'shortcuts' issues that will bite me back in the future... but an mvp is an mvp. Thanks guys for the help, though! :)
I've got a nested form in Rails 3 as follows that works just fine during creation. In the edit stage, I'm getting WARNING: Can't mass-assign protected attributes: type. The form shouldn't be trying to set "type" and isn't doing so as far as I can tell from the parameters.
class TagSetNomination < ActiveRecord::Base
belongs_to :pseud
belongs_to :owned_tag_set
has_many :fandom_nominations, :dependent => :destroy
has_many :character_nominations, :dependent => :destroy
has_many :relationship_nominations, :dependent => :destroy
has_many :freeform_nominations, :dependent => :destroy
accepts_nested_attributes_for :fandom_nominations, :character_nominations, :relationship_nominations, :freeform_nominations, {
:allow_destroy => true,
:reject_if => proc { |attrs| attrs[:tagname].blank? }
}
....
end
All of those nomination classes are subclasses of this base class:
class TagNomination < ActiveRecord::Base
belongs_to :tag_set_nomination
....
end
And here's the relevant bit of my form:
<%= error_messages_for :tag_set_nomination %>
<%= form_for(#tag_set_nomination, :url => (#tag_set_nomination.new_record? ? tag_set_nominations_path(#tag_set) : tag_set_nomination_path(#tag_set, #tag_set_nomination)), :html => {:method => (#tag_set_nomination.new_record? ? :post : :put)}) do |f| %>
<h4><%= ts("Tag Nominations") %></h4>
<fieldset class="tagset">
<dl>
<% #tag_set_nomination.character_nominations.each_with_index do |character_nomination, index| %>
<%= f.fields_for :character_nominations, character_nomination do |nom_form| %>
<%= render 'tag_nominations', :nom_form => nom_form, :tag_type => 'character', :tag_nominations_counter => index %>
<% end %>
<% end %>
</dl>
</fieldset>
....
<% end %>
And some hopefully relevant bits from the log:
Started POST "/tag_sets/1/nominations/3" for 68.175.83.208 at 2011-08-23 02:59:08 +0000
Parameters: { ... "tag_set_nomination"=>{"character_nominations_attributes"=>{"0"=>{"tagname"=>"Sam", "parent_tagname"=>"", "tagnotes"=>"", "id"=>"12"}, "1"=>{"tagname"=>"Dean", "parent_tagname"=>"", "tagnotes"=>"", "id"=>"13"}, "2"=>{"tagname"=>"Yarbld", "parent_tagname"=>"Supernatural", "tagnotes"=>"some notes", "id"=>"16"}} ... }
SQL (0.1ms) SELECT COUNT(*) FROM `tag_nominations` WHERE `tag_nominations`.`type` = 'CharacterNomination' AND (`tag_nominations`.tag_set_nomination_id = 3)
...
SQL (0.2ms) ROLLBACK
Pseud Load (0.2ms) SELECT `pseuds`.* FROM `pseuds` WHERE (`pseuds`.user_id = 8)
CharacterNomination Load (0.3ms) SELECT `tag_nominations`.* FROM `tag_nominations` WHERE `tag_nominations`.`type` = 'CharacterNomination' AND (`tag_nominations`.tag_set_nomination_id = 3)
WARNING: Can't mass-assign protected attributes: type
WARNING: Can't mass-assign protected attributes: type
WARNING: Can't mass-assign protected attributes: type
And then I get dumped back to edit with no errors in the page. D:
Any ideas welcome! I am baffled.
Can you make your type field attr_accessible in your model ?
I found 2 links which may help:
http://somethinglearned.com/articles/2006/05/24/best-practices-a-strong-case-for-attr_accessible-part-2
http://api.rubyonrails.org/classes/ActiveRecord/MultiparameterAssignmentErrors.html
Hope this helps !
Okay, I figured it out, sigh. It had zero to do with any of the code I pasted. I had a before_save callback that was setting a value to either true or false -- and of course, when it returned false, the before_save callback died and therefore the save got rolled back.
facepalm
I've created three classes to represent Books, People, and BookLoans. While I am able to show the association of People to Books through BookLoans I've been seeding my database.
I now need to save a checkout of a book. It was my intention to do this action through the book controller. Specifically, creating a loan action in the BooksController. While this makes sense to me in theory I am having a terrible time implementing the appropriate syntax.
I've added the ability to loan a book from the show view of a book. This view now contains a form which uses the loan action of the book controller to record the loan.
I've added what I believe are the appropriate methods to my Book model. With the help of theIV I have captured the appropriate information in the Controller. Unfortunately, when I press Loan on the book show view a book_loan record is no being recorded.
What am I missing?
Book Model
class Book < ActiveRecord::Base
has_many :book_loans
has_many :borrowers, :through => :book_loans, :source => :person
accepts_nested_attributes_for :book_loans, :allow_destroy => true
def loaned?
book_loans.exists?(:return_date => nil)
end
def current_borrower
if loaned?
book_loans.first(:order => "out_date desc").person
end
end
def add_loan (person_id)
book_loans.create(:book_id => id,
:person_id => person_id,
:out_date => Date.current)
end
end
Loan Method from BooksController
def loan
#book.add_loan(params[:book_loan][:person_id])
redirect_to :action => 'book', :id => params[:id]
end
Book Show View w/ Loan Form
<p>
<b>Title:</b>
<%=h #book.title %>
</p>
<p>
<b>Isbn10:</b>
<%=h #book.isbn10 %>
</p>
<p>
Currently loaned to:
<%=h borrower_name(#book) %>
</p>
<% form_for(#book) do |x| %>
<p>
<%= x.label :loan_person_id %><br/>
<%= collection_select(:book_loan, :person_id,
Person.find(:all, :order => 'name ASC'), :id, :name) %>
<%= x.submit 'Loan', :action => 'loan' %>
</p>
<% end %>
BookLoan Model
class BookLoan < ActiveRecord::Base
belongs_to :book
belongs_to :person
end
Person Model
class Person < ActiveRecord::Base
has_many :book_loans
has_many :books, :through => :book_loans
end
Development Log
Processing BooksController#update (for 127.0.0.1 at 2009-09-24 13:43:05) [PUT]
Parameters: {"commit"=>"Loan", "authenticity_token"=>"XskHLuco7Q7aoEnDfVIiYwVrMEh5uwidvJZdrMbYYWs=", "id"=>"1", "book_loan"=>{"person_id"=>"3"}}
[4;35;1mBook Columns (3.0ms)[0m [0mSHOW FIELDS FROM `books`[0m
[4;36;1mBook Load (4.0ms)[0m [0;1mSELECT * FROM `books` WHERE (`books`.`id` = 1) [0m
[4;35;1mSQL (0.0ms)[0m [0mBEGIN[0m
[4;36;1mBook Load (1.0ms)[0m [0;1mSELECT `books`.id FROM `books` WHERE (`books`.`title` = BINARY 'Dreaming in Code: Two Dozen Programmers, Three Years, 4,732 Bugs, and One Quest for Transcendent Software' AND `books`.id <> 1) LIMIT 1[0m
[4;35;1mSQL (1.0ms)[0m [0mCOMMIT[0m
Redirected to http://localhost:3000/books/1
Completed in 19ms (DB: 10) | 302 Found [http://localhost/books/1]
[4;36;1mSQL (0.0ms)[0m [0;1mSET NAMES 'utf8'[0m
[4;35;1mSQL (0.0ms)[0m [0mSET SQL_AUTO_IS_NULL=0[0m
When you has_many a model, you get access to a few extra methods, two in particular are collection.build and collection.create— build is like new, create is like create :). Have a look at the has many documentation. In this case, you could rewrite add_loan as
def add_loan (person_id)
book_loans.create(:book_id => id,
:person_id => person_id,
:out_date => Date.current)
end
or something similar.
To answer your question about what the view will be sending to the controller, it will send the params hash as usual, but if you just want to pass the person_id to add_loan, you can extract that. Assuming that the part of the view you have above is wrapped in a form for a book loan, you can access the person by params[:book_loan][:person_id]. You'll also want to do a find in that action first, or else #book is going to be nil.
Hope this helps. Cheers.
EDIT: I'm not sure if the way you have it right now works, but I think you want to change your Person model to read
class Person < ActiveRecord::Base
has_many :book_loans
has_many :books, :through => :book_loans
end
EDIT 2: Your development log says you aren't actually hitting loan, you're hitting update. A few things you could do: check to make sure you have loan as a listed resource in your routes; merge the loan action into the update action—could start getting kind of messy so I don't know if this is the best approach. I'm sure there are more, but those are the first things that pop to mind. Also, I don't know if you can add :action => 'loan' to a submit tag. I think it will just look at that as if it were an html option. You might want to change your form_for to read
<% form_for(#book), :url => { :action => 'loan' } do |x| %>
once you've made sure that the routes are in order. But as I said earlier, I'm pretty sure you will be thrown an error on that action because you haven't defined a Book.find for it.