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 K. 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 K. 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:

Add new book

<% form_tag :action => 'create' do %>

Title: <%= text_field 'book', 'title' %>

Price: <%= text_field 'book', 'price' %>

Subject: <%= text_field 'subject', 'name' %>

Description
<%= text_area 'book', 'description' %>

<%= 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 K. wrote:

<%= text_field ‘subject’, ‘name’ %>

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 K. 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’ %>

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.

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.

Oops, should have told you in the last post what happens when I do
that…

When app/views/book/new.html.erb looks like this:

Add new book

<% form_tag :action => 'create' do %>

Title: <%= text_field 'book', 'title' %>

Price: <%= text_field 'book', 'price' %>

Subject: <%= text_field_tag 'subject' %>

Description
<%= text_area 'book', 'description' %>

<%= submit_tag "Create" %> <% end %> <%= link_to 'Back', {:action => 'list'} %>

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

I am really grateful for all your help, and sorry if I’m being dense!

just curious… morgan male or morgan female?

I can tell I have frustrated you… I’m doing my best, and I really
appreciate everything you’re doing to help…

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

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’m
sorry you made the wrong assumption, but I didn’t know enough to correct
you.

b)

<%= text_field_tag ‘subject’ %>

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.

As I entered a sample entry, I used “stuff” as a sample subject. It
doesn’t matter what I put in the subject field - I get the same error
message.

Obviously I’m being a total dunce here. As I said in my first post, I
have spent two days trying to figure this out, and I’m totally stymied.
I have read every book and tutorial and blog post I can find, and I just
keep getting more and more confused. Can you point me to a good place
to learn about this?

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| %>

Title: <%= f.text_field 'title' %>

Price: <%= f.text_field 'price' %>

Subject: <%= text_field(:subject, :name) %>

Description
<%= f.text_area 'description' %>

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

<% end %>

On Jul 31, 10:30 pm, Morgan K. [email protected]

Hi Morgan,

Morgan K. 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

Bill W. 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.

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| %>

Title: <%= f.text_field 'title' %>

Price: <%= f.text_field 'price' %>

Subject: <%= text_field(:subject, :name) %>

Description
<%= f.text_area 'description' %>

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

<% end %>

On Jul 31, 10:30 pm, Morgan K. [email protected]

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.

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
On Aug 1, 2:18 pm, Morgan K. [email protected]

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!

I do not know whats happening, but somehow my post is not appearing on
this forum.

I am sending this mail so that it will help anyways:

I think I was in hurry, I should have explained the code.
So here it goes:
" || " is conditional " or "

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

“find a subject by name (here, params[:subject][:name] ).
If do not find any subject then create subject with object (here,
params[:subject]).”

Makes sense because you don’t want to create subjects unnecessarily,
if it exists.

map.resources :books
I think you would need to map resource, good that you have added :slight_smile:

“Create” button, I get:
NameError in BooksController#create
uninitialized constant BooksController

It’s typo. So the controller name is BookController and the resources
added to routes is for BooksController.
you can do either of these:

Change routes to:
map.resources :book

or

Change name of the controller:
BooksController (seems appropriate to me)

On Aug 1, 2:18 pm, Morgan K. [email protected]

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 K. 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