Another newbie - how to save related data?

Hello! This has got to be one of those easy and obvious things, but
I've been beating my head against it for two days now, and can't figure
it out.

I'm following the tutorial at
http://www.tutorialspoint.com/ruby-on-rails-2.1/index.htm, which I have
found to be quite helpful. However, I want to do things a little bit
differently, and can't figure out how. This tutorial sets up a "Books"
table, with columns Title, Price, and Description. There is another
table called "Subjects", with a column for Name. The two tables have a
one-to-many relationship, so the subject has many books.

The way the tutorial does it, you pre-populate the "Subjects" table with
a list of subjects, and then when you create a form to enter new data,
you choose the subjects from a drop-down list. I can get this to work
just fine, but I want to do something different: I don't want the list
of subjects to be pre-determined, but I want to be able to enter them in
as I go.

How on earth do you do this? I haven't found a single tutorial that
explains how to enter data into a related table without using a
drop-down list of pre-entered data!

I can do it in the console:

s=Subject.new

=> #<Subject id: nil, name: nil, created_at: nil, updated_at: nil>

s.name="Stuff"

=> "Stuff"

s.save

=> true

Subject.find(:all)

=> [#<Subject id: 1, name: "Stuff", created_at: "2008-07-31 20:35:58",
updated_at: "2008-07-31 20:35:58">]

Book.find(:all)

=> [#<Book id: 1, title: "Book", price: 5.0, subject_id: nil,
description: "Trying to figure this out.", created_at: "2008-07-31
20:26:11", updated_at: "2008-07-31 20:26:11">]

b=Book.find(id="1")

=> #<Book id: 1, title: "Book", price: 5.0, subject_id: nil,
description: "Trying to figure this out.", created_at: "2008-07-31
20:26:11", updated_at: "2008-07-31 20:26:11">

b.subject_id = "1"

=> "1"

b.save

=> true

Book.find(:all)

=> [#<Book id: 1, title: "Book", price: 5.0, subject_id: 1, description:
"Trying to figure this out.", created_at: "2008-07-31 20:26:11",
updated_at: "2008-07-31 20:37:10">]

But for the life of me I can't figure out how to do it using forms.

Any help is greatly appreciated!

Hi Morgan,

Morgan Kay wrote:

I want to do something different: I don't want the list
of subjects to be pre-determined, but I want to be able
to enter them in as I go.

How on earth do you do this? I haven't found a single
tutorial that explains how to enter data into a related
table without using a drop-down list of pre-entered
data!

The key is in the documentation (api.rubyonrails.org) for "form_for" where
it says " Also note that form_for doesn‘t create an exclusive scope. It‘s
still possible to use both the stand-alone FormHelper methods and methods
from FormTagHelper. "

The example that follows is not particulary helpful in your case since the
case all the items still address the same object. That doesn't need to be
the case. Everything in between the<% form for ...%> and <% end %> is going
to show up in your params hash. You'll probably want to use a
text_field_tag for your subject entry field and then in your controller
method you'll need to save that value to your Subjects table separately.

HTH,
Bill

Everything in between the<% form for ...%> and <% end %> is

going
to show up in your params hash. You'll probably want to use a
text_field_tag for your subject entry field and then in your controller
method you'll need to save that value to your Subjects table separately.

Bill, thank you so much for your reply! I'm afraid I'm still not quite
certain how to actually do this.... I have suspected in theory that I
need to do what you describe, but I just don't know what to actually
do....

How do I save the value to the subjects table in the controller method?
Do I define "create" in the subjects controller or the books controller?
And what should the text_field_tag look like?

Sorry for the dumb questions, but I am totally new to this! I've tried
reading the Ruby documentation, and most of the time it just doesn't
make much sense to me. As you have no doubt heard other people
complain, there are lots of tutorials/guides for people who know
nothing, and lots of guides/documentation for experts, and really
nothing in the middle, so once I've followed the instructions in the
tutorials, launching out on my own is really tough!

Thanks for your help!

Hi Morgan,
Morgan Kay wrote:

How do I save the value to the subjects table in the controller method?
Do I define "create" in the subjects controller or the books controller?
And what should the text_field_tag look like?

Starting from the last question and working up...

In your view, inside your <% form_for ... %> you'll need

<%= text_field_tag 'subject', "Enter book's subject:" %>

Technically you can create the new record in either controller because you
have access to all your models in each controller. Since you're already in
your books controller, do it there.

Assuming you're adding a new record, given the view above, after you've
saved your book record, do something like ...

book_subject = Subject.new(params[:subject])
book_subject.save

HTH,
Bill

Thanks again for your helpful and quick reply!

You seem to be using slightly different syntax than the tutorials I've
been following, so I'm not quite sure if I'm following your suggestion
correctly (this has been another major frustration in trying to learn
this stuff - different tutorials use different syntax, so it's really
hard to apply what I learn in one tutorial to a different tutorial).

Here's the relevant part of my books controller, more or less following
your suggestion (I tried it without the "@" as you suggested, and with
them as they appear below, and neither worked):

class BookController < ApplicationController
   ...
   def new
      @book = Book.new
   end
   def create
      @book = Book.new(params[:book])
      @book_subject = Subject.new(params[:subject])
      @book_subject.save
      if @book.save
            redirect_to :action => 'list'
      else
            render :action => 'new'
      end
   end
   ...
end

And here's my new.html.erb:

<h1>Add new book</h1>
<% form_tag :action => 'create' do %>
<p><label for="book_title">Title</label>:
<%= text_field 'book', 'title' %></p>
<p><label for="book_price">Price</label>:
<%= text_field 'book', 'price' %></p>
<p><label for="book_subject">Subject</label>:
<%= text_field 'subject', 'name' %></p>
<p><label for="book_description">Description</label><br/>
<%= text_area 'book', 'description' %></p>
<%= submit_tag "Create" %>
<% end %>
<%= link_to 'Back', {:action => 'list'} %>

Hitting the "Create" button works just fine, but no data is being saved
to the Subject table.

Any idea what I need to do?

Thanks again!
Morgan

Morgan Kay wrote:

<%= text_field 'subject', 'name' %></p>

You need to use text_field_tag, not text_field, and you don't need the
second param to text_field. You're still referencing it as a bound
parameter. Doing it as I suggested moves the object outside the object
scope of the form and makes it available as a standalone param which is how
you're referencing it in the controller.

HTH,
Bill

Morgan Kay wrote:

a) You didn't let me know you're using form_tag rather than form_for as I
assumed repeatedly, explicitly, you were doing.

b)

<%= text_field_tag 'subject' %></p>

and I use the form to create a new entry with a subject, I get this
error:

NoMethodError in BookController#create
undefined method `stringify_keys!' for "stuff":String

stuff? Your console example used this for a value. The error message above
says you're now using it for a key.

just curious.. morgan male or morgan female?

I think I have faced this issue recently, and this worked for me.

Assumption:
I assumed that since it is belongs_to-has_many relationship, book
table would have a column "subject_id" mapped to subject table column
"id".

Changes:
I have changed couple of lines in your Controller code and changed the
view. It should work for you.

---------------Controller-----------:

def new
    @book = Book.new
end
def create
  @subject = (Subject.find_by_name(params[:subject][:name]) ||
Subject.create(params[:subject]))
  @book = Book.new(params[:book].merge(:subject_id => @subject.id))

  respond_to do |format|
   if @book.save
     redirect_to(:action => :list)
   else
     render :action => "new"
   end
  end
end

---------------View-------------
<% form_for(@book) do |f| %>

<p><label for="book_title">Title</label>:
<%= f.text_field 'title' %></p>

<p><label for="book_price">Price</label>:
<%= f.text_field 'price' %></p>

<p><label for="book_subject">Subject</label>:
<%= text_field(:subject, :name) %></p>

<p><label for="book_description">Description</label><br/>
<%= f.text_area 'description' %></p>

<p><%= f.submit "Create"%>
<%= link_to 'Back', {:action => 'list'} %></p>
<% end %>

Hi Morgan,

Morgan Kay wrote:

I can tell I have frustrated you...

I apologize. I should have exercised better judgement and waited until I
was in better humour to reply. Not your fault at all. Hope you'll forgive
me.

I am so incredibly lost. Some tutorials have said
to use "form_tag", some have said to use "form_for",
and I don't know the difference.

I've got a meeting this morning I'm still preparing for so I'll have to keep
this short for now. I'll check back after lunch. The basic difference
between form_for and form_tag is that when you use the former you're telling
Rails, up front, which model the form will be referencing. That impacts the
amount of 'magic' Rails can do for you. It seemed to me that you were
expecting Rails to save the associated record (Subject) for you which is why
I assumed you were using form_for. The reply from gouravtiwari21 uses
form_for and illustrates that 'magic'. In thinking back on my advice, I
don't think the params assignment I suggested would have worked correctly
for form_for anyway. Probably should have worked for form_tag though. But
I'll have to get back to you later to go through that.

Can you point me to a good place to learn about this?

If the tutorials aren't helping, this is the right place :wink: I'll check
back later. Hang in there!

Best regards,
Bill

gouravtiwari21 wrote:

I think I have faced this issue recently, and this worked for me.

Assumption:
I assumed that since it is belongs_to-has_many relationship, book
table would have a column "subject_id" mapped to subject table column
"id".

Changes:
I have changed couple of lines in your Controller code and changed the
view. It should work for you.

---------------Controller-----------:

def new
    @book = Book.new
end
def create
  @subject = (Subject.find_by_name(params[:subject][:name]) ||
Subject.create(params[:subject]))
  @book = Book.new(params[:book].merge(:subject_id => @subject.id))

  respond_to do |format|
   if @book.save
     redirect_to(:action => :list)
   else
     render :action => "new"
   end
  end
end

---------------View-------------
<% form_for(@book) do |f| %>

<p><label for="book_title">Title</label>:
<%= f.text_field 'title' %></p>

<p><label for="book_price">Price</label>:
<%= f.text_field 'price' %></p>

<p><label for="book_subject">Subject</label>:
<%= text_field(:subject, :name) %></p>

<p><label for="book_description">Description</label><br/>
<%= f.text_area 'description' %></p>

<p><%= f.submit "Create"%>
<%= link_to 'Back', {:action => 'list'} %></p>
<% end %>

On Jul 31, 10:30 pm, Morgan Kay <rails-mailing-l...@andreas-s.net>

This definitely looks closer to working than anything else I have tried!
It makes so much sense to tell the controller what to do. (Everything
else I have read says "oh, just put has_may and belongs_to in the model
and Rails will do everything for you!", and I have never quite believed
that....)

I do have some questions, though. First, just for clarification, in
these lines:

  @subject = (Subject.find_by_name(params[:subject][:name]) ||
Subject.create(params[:subject]))

what are the "||" doing? I don't think I have seen that before
anywhere....

Secondly, when I used this code, I went to ...books/new and got a No
Method Error, so I had to add "map.resources :books" to
config/routes.rb, and then it worked just fine. Why does this happen?
The tutorial that I have been following for this application never edits
config/routes.rb, and the tutorial works just fine, but other tutorials
I have followed don't work without mapping resources. I understand what
adding "map.resources :books" does, I just don't understand why you
don't have to do it for some applications, or which of your suggested
changes made it necessary to do this. (Does that question make sense?
I can clarify if I need to....)

And finally, I can't quite get this to work still. When I hit the
"Create" button, I get:
NameError in BooksController#create
uninitialized constant BooksController
I'll keep chugging away trying to figure out why I'm getting that error,
but if it's something obvious, let me know.

Thanks so much for your help!
Morgan.

Bill Walton wrote:

I apologize. I should have exercised better judgement and waited until
I
was in better humour to reply. Not your fault at all. Hope you'll
forgive
me.

I certainly wasn't in the most rational of moods myself - I was really
really frustrated. I'm feeling much more logical today.

If the tutorials aren't helping, this is the right place :wink: I'll check
back later. Hang in there!

Thanks again for all your help and encouragement!
Morgan.

I should say I haven't explained it, was in hurry...but here is what I
meant:

> @subject = (Subject.find_by_name(params[:subject][:name]) ||

Subject.create(params[:subject]))

what are the "||" doing? I don't think I have seen that before
anywhere....

Look closely at this line ( " || " is nothing but " or " ):
Subject.find_by_name(params[:subject][:name]) ||
Subject.create(params[:subject])

It reads:
' find a Subject by subject name.
If do not find a subject then create the subject with object "
params[:subject] ". '

Map Resources:
I think you have to add resources and good that you have added :slight_smile:

And finally, I can't quite get this to work still. When I hit the
"Create" button, I get:
NameError in BooksController#create
uninitialized constant BooksController

It's typo :slight_smile:
I thought that your controller name is BooksController but it is
BookController

So you can do either of these:
1. Change your controller's name from BookController to
BooksController (Looks appropriate to me)
or
2. If you do not change controller name then change your routes.rb :
map.resources :book

and restart server.

I think it should work.
Regards
gouravtiwari21

Thank you for all of this information! I've had a busy day, and getting
ready to head out of town for the weekend, but I'll come back to this on
Monday. Everything you have said makes a lot of sense. Thanks!
Morgan.

gouravtiwari21 wrote:

I should say I haven't explained it, was in hurry...but here is what I
meant:

> @subject = (Subject.find_by_name(params[:subject][:name]) ||

Subject.create(params[:subject]))

what are the "||" doing? I don't think I have seen that before
anywhere....

Look closely at this line ( " || " is nothing but " or " ):
Subject.find_by_name(params[:subject][:name]) ||
Subject.create(params[:subject])

It reads:
' find a Subject by subject name.
If do not find a subject then create the subject with object "
params[:subject] ". '

Neat! This is really handy stuff to know!

Map Resources:
I think you have to add resources and good that you have added :slight_smile:

I'm still not sure why I had to add resources: what is different between
my code and yours that made adding resources necessary?

And finally, I can't quite get this to work still. When I hit the
"Create" button, I get:
NameError in BooksController#create
uninitialized constant BooksController

It's typo :slight_smile:
I thought that your controller name is BooksController but it is
BookController

So you can do either of these:
1. Change your controller's name from BookController to
BooksController (Looks appropriate to me)

I did this, so now it's called "BooksController" and I think I have
changed all of the relevant directory names, etc.

Now I am having another problem, and I have been beating my head against
it all morning and getting nowhere. Any time I try to do anything with
my application, I get this error:

.../library/app/controllers/books_controller.rb:42: syntax error,
unexpected kEND, expecting $end

I'm quite familiar with this error message, having seen it an awful lot
already. However, I can't find any unclosed parentheses or brackets or
anything in my code, and somehow ruby-debug just can't deal with this
file. Do you see anything missing in this code?

class BooksController < ApplicationController
   def list
      @books = Book.find(:all)
   end
   def show
      @book = Book.find(params[:id])
   end
   def new
      debugger
      @book = Book.new
   end
   def create
      @subject = (Subject.find_by_name(params[:subject][:name]) ||
Subject.create(params[:subject]))
      @book = Book.new(params[:book].merge(:subject_id => @subject.id))
        if @book.save
          redirect_to(:action => :list)
        else
          render :action => 'new'
        end
      end
   end
   def edit
      @book = Book.find(params[:id])
      @subjects = Subject.find(:all)
   end
   def update
      @book = Book.find(params[:id])
      if @book.update_attributes(params[:book])
         redirect_to :action => 'show', :id => @book
      else
         @subjects = Subject.find(:all)
         render :action => 'edit'
      end
   end
   def delete
      Book.find(params[:id]).destroy
      redirect_to :action => 'list'
   end
   def show_subjects
      @subject = Subject.find(params[:id])
   end
end

Many thanks for all your help!

Now I am having another problem, and I have been beating my head against
it all morning and getting nowhere. Any time I try to do anything with
my application, I get this error:

.../library/app/controllers/books_controller.rb:42: syntax error,
unexpected kEND, expecting $end

I'm quite familiar with this error message, having seen it an awful lot
already. However, I can't find any unclosed parentheses or brackets or
anything in my code, and somehow ruby-debug just can't deal with this
file. Do you see anything missing in this code?

Duh, finally found it. There was an extra "end" in there.

It finally works! I am so thrilled! I can now enter books and
subjects!

The application as a whole still has problems, though. Mapping
resources has messed up a bunch of other stuff. For instance, in the
controller, I have defined "list", and I have a "list.html.erb." Before
I mapped resources, I could go to localhost:3000/books/list, and it
would render "list.html.erb." Now that I have mapped resources, when I
go to localhost:3000/books/list, it says "Couldn't find Book with
ID=list." localhost:3000/books/new takes me to "new.html.erb," but
"list" and "show" and other views don't work. What's going on here?

Thank you!!

Hi Morgan,

Morgan Kay wrote:

Before I mapped resources, I could go to localhost:3000/books/list,
and it would render "list.html.erb." Now that I have mapped resources,
when I go to localhost:3000/books/list, it says "Couldn't find Book with
ID=list." localhost:3000/books/new takes me to "new.html.erb," but
"list" and "show" and other views don't work. What's going on here?

In a word: REST. You'll want to study up on it. There's quite a bit in the
archives, and some good stuff in blogs you can find via Google.

Also, if I can offer a suggestion, it's best to start a new thread for
different topics. It'll make it easier for folks who come later who are
having a similar problem, and you'll get the attention of folks on the list
who might not have known the answer to your previous problem but _are_ able
to help with your new one.

HTH,
Bill