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 Ruby on Rails 2.1 Tutorial, 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