Rails 3 - dependent => :delete_all doesn't work. Why? - ruby-on-rails

I have few models in my project. There are some of them: Qualification and Curriculum. Qualification has children (curriculums). I want to make sure that when i delete Qualification i delete all it's children. Here is me code:
# Table name: qualifications
#
# id :integer not null, primary key
# subject_id :integer
# teacher_id :integer
# created_at :datetime not null
# updated_at :datetime not null
class Qualification < ActiveRecord::Base
belongs_to :subject
belongs_to :teacher
has_many :curriculums, :dependent => :delete_all
has_many :school_classes, :through => :curriculums
end
# id :integer not null, primary key
# school_class_id :integer
# qualification_id :integer
# created_at :datetime not null
# updated_at :datetime not null
class Curriculum < ActiveRecord::Base
belongs_to :school_class
belongs_to :qualification
has_one :result
has_many :timetables
end
As you can see i tried to use :dependent => :delete_all in Qualification model. But it doesn't work. Why?
UPD:
I deleting qualification by unchecking checkboxes in view when i EDIT it:
<div class="control-group">
<%= f.label :subject_ids, "Teacher can teach such subjects in the school",
:class => "control-label" %>
<div class="controls">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Choose</th>
<th>Subject</th>
</tr>
</thead>
<tbody>
<%= hidden_field_tag "teacher[subject_ids][]", nil %> <%# We use hidden field because it doesn't submit unchecked fields. So, we pass nil and nothing will be submitted.%>
<% #subjects.each do |subject| %>
<tr>
<td>
<%= check_box_tag "teacher[subject_ids][]", # [] brackets tells that this is array.
subject.id, # Value of checkbox.
#teacher.subject_ids.include?(subject.id), # Here we automatically check checkboxes.
id: dom_id( subject ) %> <%# Give unique id for each value. 'dom_id' is Rails helper. We will have ids like: 'subject_1', 'subject_2' and etc. %>
</td>
<td>
<%= label_tag dom_id( subject ), subject.subject_name %> <%# Put name of subject. %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
Here is more info:
class Teacher < ActiveRecord::Base
has_many :qualifications
has_many :subjects, :through => :qualifications
end
class Subject < ActiveRecord::Base
has_many :qualifications
has_many :teachers, :through => :qualifications
end
Here is SQL code when i update my model:
Started PUT "/teachers/2" for 127.0.0.1 at 2012-06-03 18:34:44 +0400
Processing by TeachersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZJNNV9/TO6k18O1Ar1kpkU+PWbd7btHm9Tc067iMNO4=", "teacher"=>{"teacher_last_name"=>"Last", "teacher_first_name"=>"First", "teacher_middle_name"=>"Middle", "teacher_sex"=>"m", "teacher_birthday"=>"1980-12-01", "teacher_phone_attributes"=>{"teacher_mobile_number"=>"88283686", "teacher_home_number"=>"5112787", "id"=>"2"}, "teacher_education_attributes"=>{"teacher_education_university"=>"Mmm", "teacher_education_year"=>"1970-01-01", "teacher_education_graduation"=>"Graduated", "teacher_education_speciality"=>"Math", "id"=>"2"}, "teacher_category"=>"1st", "subject_ids"=>["", "4", "3", "1"]}, "commit"=>"Update", "id"=>"2"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Teacher Load (0.4ms) SELECT "teachers".* FROM "teachers" WHERE "teachers"."id" = $1 LIMIT 1 [["id", "2"]]
(0.1ms) BEGIN
Subject Load (0.5ms) SELECT "subjects".* FROM "subjects" WHERE "subjects"."id" IN (4, 3, 1)
Subject Load (0.5ms) SELECT "subjects".* FROM "subjects" INNER JOIN "qualifications" ON "subjects"."id" = "qualifications"."subject_id" WHERE "qualifications"."teacher_id" = 2
SQL (0.4ms) DELETE FROM "qualifications" WHERE "qualifications"."teacher_id" = 2 AND "qualifications"."subject_id" = 2
TeacherPhone Load (0.4ms) SELECT "teacher_phones".* FROM "teacher_phones" WHERE "teacher_phones"."teacher_id" = 2 LIMIT 1
TeacherEducation Load (0.4ms) SELECT "teacher_educations".* FROM "teacher_educations" WHERE "teacher_educations"."teacher_id" = 2 LIMIT 1
(24.1ms) COMMIT
Redirected to http://0.0.0.0:3000/teachers
Completed 302 Found in 57ms (ActiveRecord: 27.2ms)

Try:
:dependent => :destroy
instead of :dependent => :delete_all

Not sure if this is what you are doing but I'm able to delete dependents using :dependent => :delete_all
Instead of using delete, I used destroy in my controller.
Example:
Instead of:
Qualification.delete(params[:id])
Use this:
Qualification.destroy(params[:id])
Hope this helps. =)

the "the rails 3 way" says that -
(pg 187)
clear
Transactionally removes all records from this association by clearing the foreign key
field (see delete). If the association is configured with the :dependent option set to
:delete_all, then it calls delete_all. Similarly, if the :dependent option is set to
:destroy_all, then the destroy_all method is invoked.
delete(*records) and delete—all
The delete and delete_all methods are used to sever specified associations, or all of
them, respectively. Both methods operate transactionally.
It’s worth noting, for performance reasons, that calling delete_all first loads the
entire collection of associated objects into memory in order to grab their ids. Then it
executes a SQL UPDATE that sets foreign keys for all currently associated objects to nil,
effectively disassociating them from their parent. Since it loads the entire association into memory, it would be ill-advised to use this method with an extremely large collection of associated objects.
Note
The names of the delete and delete_all methods can be misleading. By default, they
don’t delete anything from the database—they only sever associations by clearing the foreign
key field of the associated record. This behavior is related to the :dependent option, which
defaults to :nullify. If the association is configured with the :dependent option set to
:delete or :destroy, then the associated records will actually be deleted from the database.
:dependent => :destroy or :delete
Specifies a rule that the associated owner record should be destroyed or just deleted from
the database, depending on the value of the option. When triggered, :destroy will call
the dependent’s callbacks, whereas :delete will not.
Usage of this option might make sense in a has_one / belongs_to pairing. However,
it is really unlikely that you want this behavior on has_many / belongs_to relationship;
it just doesn’t seem to make sense to code things that way. Additionally, if the owner
record has its :dependent option set on the corresponding has_many association, then
destroying one associated record will have the ripple effect of destroying all of its siblings.

Related

Querying a join table?

I am trying to do two things:
query an attribute from an inner join table in Rails' Console.
query and displaying the attribute in a view.
These are my Models:
retreat.rb:
class Retreat < ApplicationRecord
belongs_to :user
belongs_to :account
validates :name, presence: true
has_many :retreats_teams
has_many :teams, through: :retreats_teams
accepts_nested_attributes_for :retreats_teams
end
retreats_team.rb:
class RetreatsTeam < ApplicationRecord
belongs_to :team
belongs_to :retreat
end
team.rb:
class Team < ApplicationRecord
belongs_to :account
has_many :team_members
has_many :users, through: :team_members
accepts_nested_attributes_for :team_members
has_many :retreats
has_many :retreats, through: :retreats_teams
end
In Rails' console, if I type:
Retreat.last.teams
I get the output:
irb(main):008:0> Retreat.last.teams
Retreat Load (0.9ms) SELECT "retreats".* FROM "retreats" ORDER BY "retreats"."id" DESC LIMIT $1 [["LIMIT", 1]]
Team Load (0.9ms) SELECT "teams".* FROM "teams" INNER JOIN "retreats_teams" ON "teams"."id" = "retreats_teams"."team_id" WHERE "retreats_teams"."retreat_id" = $1 LIMIT $2 [["retreat_id", 38], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Team id: 56, name: "My house", account_id: 2, created_at: "2020-02-10 15:57:25", updated_at: "2020-02-10 15:57:25">]>
irb(main):009:0>
How do I retrieve the team name: "My house"?
Also, there might be many teams that display here, too.
#teams returns a collection of team objects. The simplest solution is to call first on the teams to get the first team in the collection:
Retreat.last.teams.first.name
=> "My house"
But if you want all the names in teams you might use pluck. This will allow you to do this:
retreat = Retreat.last
foo = Team.create(name: 'Foo')
bar = Team.create(name: 'Bar')
retreat.teams << foo
retreat.teams << bar
retreat.teams.pluck(:name).to_sentence
=> "My house, Foo, and Bar"
A word on naming
The naming convention for join models is SingularSingular. The table should be named singular_plural. has_and_belongs_to_many is the only part of Rails that actually uses the oddball plural_plural naming scheme.
RetreatsTeam # bad
RetreatTeam # better
Even better though is to actually give your join tables meaningful names instead of just placeholder names.
1) querying an attribute from an inner join table in Rails Console.
Since the association between Retreat and RetreatsTeams in one to many you can actually only fetch aggregates. Otherwise which attribute should it fetch, from the first row, the last row or all the rows?
So for example you can do:
Retreat.joins(:retreats_teams)
.select('retreats.*', 'COUNT(retreats_teams.*) AS retreats_teams_count')
If you are storing more data on the join table that you want to display you want to iterate through the join table:
#retreat = Retreat.eager_load(retreats_teams: :teams).first
#retreat.retreats_teams.each do |rt|
puts rt.foo
puts rt.team.name
end
2) querying and displaying the attribute in a view.
In Rails you're usually just fetching records in the controller and then iterating through them in the view:
class ResortsController < ApplicationController
def show
#resort = Resort.includes(:teams).find(params[:id])
end
end
# app/views/resorts/show.html.erb
<h1><%= #resort.name %></h1>
<h2>Teams</h2>
<% if #resort.teams.any? %>
<ul>
<% #resort.teams.each do |team| %>
<li><%= team.name %></li>
<% end %>
</ul>
<% else %>
<p>This resort has no teams</p>
<% end %>

many-to-many with multiple select in Rails

I have a model called Article, another model called Organization, and a join table called ArticleAllowedOrganization that has an article_id and organization_id in it.
When a user creates an article I would like for them to specify which organizations should have access to the article.
When I test in the console it seems to be working correctly, however, when I create an article in the web app the params for the through association allowed_organization_ids is empty.
Article model:
class Article < ActiveRecord::Base
has_many :article_allowed_organizations, dependent: :destroy
has_many :allowed_organizations, through: :article_allowed_organizations, source: :organization
end
Organization model:
class Organization < ActiveRecord::Base
has_many :article_allowed_organizations, dependent: :destroy
has_many :allowed_articles, through: :article_allowed_organizations, source: :article
end
the join table:
class ArticleAllowedOrganization < ActiveRecord::Base
belongs_to :article
belongs_to :organization
end
in the articles_controller.rb I permitted the array for allowed_organization_ids
def article_params
params.require(:article).permit(:other_stuff, :allowed_organization_ids => [])
end
In the form:
<%= f.collection_select(:allowed_organization_ids, Organization.order(:name), :id, :name,
{include_blank: true}, {multiple: true}) %>
In the console I can manually set multiple organization id's to be associated with an article and it works.
a = Article.last
a.allowed_organization_ids = [2, 4]
you can see below that it inserts the ids for an organization into the ArticleAlllowedOrganization table when I use the console.
(0.3ms) BEGIN
SQL (1.3ms) INSERT INTO "article_allowed_organizations" ("article_id", "organization_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["article_id", 95], ["organization_id", 4], ["created_at", "2018-12-12 17:47:25.978487"], ["updated_at", "2018-12-12 17:47:25.978487"]]
(6.5ms) COMMIT
=> [2, 4]
when using the web app I get this in the log where the allowed_organization_ids isn't passing anything.
Parameters: {"....some_other_params" "allowed_organization_ids"=>[""]}
if I try and use collection_check_boxes
<%= f.collection_check_boxes :allowed_organization_ids, Organization.all, :id, :name do |b| %>
<div class="collection-check-box">
<%= b.check_box %>
<%= b.label %>
</div>
<% end %>
I get this in the log when using collection_check_boxes where it says true instead of the organization ids
"allowed_organization_ids"=>["true"]
also what's interesting is that I also get the below in th elog when trying to use checkboxes where it's trying to find an organization that has an id of 0 but can't which prevents the form from submitting.
[1m[35mOrganization Load (0.5ms)[0m SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT 1 [["id", 0]]
Completed 404 Not Found in 67ms (ActiveRecord: 2.4ms)
ActiveRecord::RecordNotFound (Couldn't find Organization with 'id'=0):
Please check follow are few issues. In your controller, you put
def article_params
params.require(:article).permit(:other_stuff, :allowed_organization_ids => [])
end
So you are already saying your allowed_organization_ids is empty, please replace with following
def article_params
params.require(:article).permit(:other_stuff, :allowed_organization_id)
end
I think you are just changing the name, so you have provided the different name of classes but here in the relationship you are mentioning different, but as your console is giving correct relationship this should not be an issue, but in case I am mentioning it.
class ArticleAllowedOrganization < ActiveRecord::Base
belongs_to :article
belongs_to :organization
end
should be allowed_article and allowed_organization
I am practicing so I created this in the project to see if it is working with proper names, so following are commands I used in the generator to create this whole project. You can try to see if there is any other error. Note I put permission field in the allowed table, all commands are on command line inside your project first command will create new project.
rails new SampleProject && cd $_ && bundle
rails g model Article title:string description:text
rails g model Organization name:string
rails g model Allowed permission:string article:references organization:references
Now make sure your Article model has following.
class Article < ActiveRecord::Base
has_many :alloweds
has_many :organizations, through: :alloweds
end
And in your Organization model
class Organization < ActiveRecord::Base
has_many :alloweds
has_many :articles, through: :alloweds
end
And in your allowed model, although I will use permission than allowed table name but it is your naming.
class Allowed < ActiveRecord::Base
belongs_to :article
belongs_to :organization
end
Now let us generate views and controllers
rails g scaffold_controller Article title:string description:string
rails g scaffold_controller Organization name:string
rails g scaffold_controller Article title:string description:string
Now put routes in your config/routes.rb
resources :articles
resources :organizations
resources :alloweds
Now you can start your rail server and navigate to /articles , /organization and /alloweds to see it working. Rails auto generator uses text field for relationship ids, so you can put manually ids and it will work.
Hope it is clear.

Rails not ordering by date for this query

I'm currently working on a website where a parent can reserve classes for their kids. In the parent's dashboard, I'd like to show a table of schedules and in every row there will be the child's name associated with a schedule. Unfortunately I have problem ordering the schedules by date in the parent's dashboard.
# view
<% #reservations.each do |reservation| %>
<%= reservation.child.first_name %>
<%= reservation.schedule.date %>
<%= reservation.schedule.start_time %> - <%= reservation.schedule.end_time %>
<%= link_to reservation.schedule.klass.name, schedule_path(reservation.schedule) %></td>
<%= link_to reservation.schedule.partner.company, partner_path(reservation.schedule.partner) %></td>
<%= reservation.schedule.city.name %></td>
<% end %>
# associations
User
has_many :children, dependent: :destroy
has_many :reservations
has_many :schedules, through: :reservations
Child
belongs_to :user
has_many :reservations, dependent: :destroy
has_many :schedules, through: :reservations
Reservation
belongs_to :child
belongs_to :schedule
belongs_to :user
Schedule
belongs_to :city
belongs_to :partner
belongs_to :activity
belongs_to :klass
has_many :reservations
has_many :children, through: :reservations
I've got a default scope in my Schedule model that orders by date, start_time and end_time.
# Schedule model
default_scope { order(:date, :start_time, :end_time) }
This scope works in other tables, but not for this query:
# controller
#reservations = current_user.reservations.includes(:child, schedule: [:partner, :klass, :city])
It just refuses to order by date and time in the browser:
The log shows that the query for Schedule is indeed being ordered:
Reservation Load (0.3ms) SELECT "reservations".* FROM "reservations" WHERE "reservations"."user_id" = $1 [["user_id", 1]]
Child Load (0.4ms) SELECT "children".* FROM "children" WHERE "children"."id" IN (1, 2, 3)
Schedule Load (0.4ms) SELECT "schedules".* FROM "schedules" WHERE "schedules"."id" IN (24, 12) ORDER BY "schedules"."date" ASC, "schedules"."start_time" ASC, "schedules"."end_time" ASC
Partner Load (0.3ms) SELECT "partners".* FROM "partners" WHERE "partners"."id" IN (2)
Klass Load (0.3ms) SELECT "klasses".* FROM "klasses" WHERE "klasses"."id" IN (9, 17)
City Load (0.4ms) SELECT "cities".* FROM "cities" WHERE "cities"."id" IN (28)
I could do this query in the controller instead:
#schedules = current_user.schedules
but then I'd have problem showing only a single child's name for each schedule since a class schedule can have many children associated for it.
Help?
You've defined an ordering on the default scope for Schedule, but you are pulling your relation from Reservation. As a result, the schedules are queried in the order specified, but since presumably you're looping through the #reservations in your view, you see their order and not their schedules's order. You can order #reservations by fields on the schedules table like so:
#reservations = #reservations.order("schedules.date ASC, schedules.start_time ASC, schedules.end_time ASC")
Also note that defining a default_scope in the model is generally discouraged (on SO and elsewhere) because its difficult to manage and can cause unexpected and unintuitive effects, especially down the road.

How to write properly a nested form with a N-N relation

While working on Rails 3 app, I came to the problem of nested forms.
One collection is a set of predefined objects (created with db:seed).
The other collection should show a form to allow to choose a few elements.
An example is better than a long description, so here it is.
Suppose you have 2 models: User and Group.
Suppose there are a few groups: Member, Admins, Guests, ...
You want your users to have multiple groups, and so you need a intermediate table: memberships.
The model code is obvious:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
The controller should not need to be changed.
The view, however is more complicated.
I want to show a list of checkboxes to choose a few groups of the predefined ones.
I am using here the special _destroy field, with reversed value, to destroy when it is actually unchecked (and so add the user to the group when it is checked)
%p
= f.label :name
%br
= f.text_field :name
%ul
= f.fields_for :memberships, #groups do |g|
%li
- group = g.object
= g.hidden_field :group_id, :value => group.id
= g.check_box :_destroy, {:checked => #user.groups.include?(group)}, 0, 1
= g.label :_destroy, group.name
However, this do not work as expected, because the form g will always create an input with an arbitrary id after each group (and even break the layout by including it after the </li>):
<input id="user_memberships_attributes_0_id" name="user[memberships_attributes][0][id]" type="hidden" value="1" />
<input id="user_memberships_attributes_1_id" name="user[memberships_attributes][1][id]" type="hidden" value="2" />
# ...
Knowing the syntax of nested attributes is the following:
{:group_id => group.id, :_destroy => 0} # Create
{:group_id => group.id, :_destroy => 0, :id => membership.id} # Update
{:group_id => group.id, :_destroy => 1, :id => membership.id} # Destroy
{:group_id => group.id, :_destroy => 1} # Do nothing
Sending every time the id will not work, because it will try to update a record which does not exist instead of creating it, and try to destroy when the record does not exist.
The current solution I found is to remove all the ids, which are wrong anyway (they should be the ids of the memberships, instead of simple indexes), and add the real id when the user already has the group.
(this is called in the controller before create and update)
def clean_memberships_attributes
if membership_params = params[:user][:memberships_attributes]
memberships = Membership.find_all_by_user_id params[:id]
membership_params.each_value { |membership_param|
membership_param.delete :id
if m = memberships.find { |m| m[:group_id].to_s == membership_param[:group_id] }
membership_param[:id] = m.id
end
}
end
end
This seems so wrong, and it adds a lot of logic in the controller, just to control the bad behavior of the view fields_for helper.
Another solution is to create all the form html yourself, trying to mimic the Rails conventions, and avoid the id problem, but that is really noisy in the code and I believe there is a better way.
Is it a way to make fields_for work better?
Is there any helper more appropriate ?
Am I reasoning wrong somewhere in this question?
How would you do to achieve this?
Thanks
I hope I understand you correctly?
The groups are predefined and you want to be able to add a user to a group.
On the edit User screen you show all or some of the predefined groups.
You want to add a user by ticking the checkbox and saving the record.
If you untick the checkbox the membership of this user in the unticked group should be nil.
Here's how I am doing this with companies and projects:
Class Company
has_many :datasets
has_many :projects, :through => :datasets
Class Project
has_many :datasets
has_many :companies, :through => :datasets
<% for company in Company.all %>
<tr>
<td>
<%= check_box_tag 'project[company_ids][]', company.id, #project.companies.include?(company) %>
</td>
</tr>
<% end %>
I list all Companies and check the ones I want to include in the project.
Please tell me if this already helps you? I believe you are using haml in your example. I am not really used to that notation.
If you want to use a subset you could use a scope:
scope :recent, Company.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight)
Then you can use this scope like the .all method:
Company.recent
Does this help?

How to save an associating record between has-many classes in Ruby on Rails

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.

Resources