Topic: inserting a different table text box into a form view?

So I'm slowly moving forward on this Rails app I'm building and I've realized that I need have a form view include an editable text box from a separate table. Originally, I had my app working so that there was a single table (called "story) in this view that contained a title, an author, and a description. Now, I've realized that I want the author to be a separate table so I can have a single author assigned to multiple stories.

Now I know I can create a controller to add authors, and eventually I will, but currently I want all the creation & editing of author name to happen within the story form view. In other words, I want these three fields:

Title (story.title)
Author (author.name)
Description (story.description)

to all be editable in the same view (either edit or create) using the same "save" button etc...

This is what the code for the title & description looks like:

<p><label for="story_title">Title</label><br/>
<%= text_field 'story', 'title'  %></p>

<p><label for="story_description">Description</label><br/>
<%= text_area 'story', 'description'  %></p>


and within my models, story belongs_to :author and author has_many :stories

How do I hook up that text area for author.name to work right?

Last edited by dsinker (2006-11-05 16:48:47)

Re: inserting a different table text box into a form view?

You can do this in the view:

<%= text_field :author, :name %>

This will look for an @author instance variable, which you can make in the new/edit actions:

def new
  @story = Story.new
  @author = Author.new
end

def edit
  @story = Story.find(params[:id])
  @author = @story.author
end


The difficult part is the create/update actions. You need to make a few decisions on how you want this to be handled. For example, if someone types the name of an author which doesn't exist, should a new one be created? What if someone change the name of the author in the edit form, should this change the name of current author, switch to a different author with that name if it exists, or create a new author? I would seriously consider using collection_select to select the author in the Story form, and creating a separate controller for adding/editing authors. I think it will  be easier.

Railscasts - Free Ruby on Rails Screencasts

Re: inserting a different table text box into a form view?

The difficult part is the create/update actions. You need to make a few decisions on how you want this to be handled. For example, if someone types the name of an author which doesn't exist, should a new one be created?

Yes, a new author should be created when typed in this form, if the author doesn't already exist.

What if someone change the name of the author in the edit form, should this change the name of current author, switch to a different author with that name if it exists, or create a new author?

That one's trickier. Generally speaking, if they're changing an author name, I think it's safe to assume that either (a) they want to create a new author or (b) they want to switch to a different author if it exists.

I'd really like to steer clear of a separate controller as the default UI on author, since the nature of this app, a story and an author are most quickly created simultaneously. I don't want to slow people down by having to switch screens to do simple things.

Re: inserting a different table text box into a form view?

Also, thanks for the code!

However, it doesn't seem to be doing what I'm expecting it to do.

It DOES create the text field flawlessly. However, when I look at the DB, the information entered into the text field isn't passing through and creating a new author name row or ID (doing an SQL query reveals, in fact, that the author table is ready to go, but contains no data.

Then, when I save it and go back to edit, the author name field is blank again.

Re: inserting a different table text box into a form view?

dsinker wrote:

It DOES create the text field flawlessly. However, when I look at the DB, the information entered into the text field isn't passing through and creating a new author name row or ID (doing an SQL query reveals, in fact, that the author table is ready to go, but contains no data.

Yeah, I didn't provide the saving portion of the code since I wasn't certain how you wanted to handle it. This will actually be easier to do than what I initially expected. Try this in the Story model:

# in story.rb
def author_name
  author.name unless author.nil?
end

def author_name=(name)
  self.author = Author.find_or_create_by_name(name)
end


This will create what I call a "fake attribute". On the outside it looks like we are just storing the author name as a text field in the stories table, but underneath we are actually going through the author's table. Since the form helper methods only care about the interface side of things, we can feed it this fake author_name attribute. Like this:

<%= text_field :story, :author_name %>

You shouldn't need to set the @author instance variable anymore now inside the new/edit actions. You also won't need to do anything special in create/update since it will be handled in the Story model when the author name is set.

Railscasts - Free Ruby on Rails Screencasts

Re: inserting a different table text box into a form view?

Oh my god, that worked like MAGIC.

Re: inserting a different table text box into a form view?

OK, now the next trick. How would I then transform that text box into a text_field_with_auto_complete?

I've got the javascript defaults loading in my head for this form, and I'm calling the code in the form itself:

<%= text_field_with_auto_complete :story, :author_name %>

And I'm sticking this as the topmost item in the Story_admin controller (which is the controller for this view)

class StoryAdminController < ApplicationController
      auto_complete_for :story, :author_name

But... nothing.

Re: inserting a different table text box into a form view?

If you take a look at the source for auto_complete_for you can see it is generating an SQL query looking for the column author_name in the stories table. Since this isn't actually a column in the database, no wonder things aren't working. I bet if you go to http://localhost:3000/story_admin/auto_complete_for_story_author_name you will see an error.

I think the easiest fix is to create an auto-completion for just the author's name, then alias that generated method to work for the story author_name auto completion. Like this:

# in controller
auto_complete_for :author, :name
alias_method :auto_complete_for_story_author_name, :auto_complete_for_author_name

Edit: Strike that, I don't think that will work because the auto-complete field won't be sending the parameters that the method expects. Alternatively you can generate the method manually like this:

def auto_complete_for_story_author_name
  @authors = Author.find(:all, :conditions => ["LOWER(name) LIKE ?", '%' + params[:story][:author_name].downcase + '%'], :order => 'name', :limit => 10)
  render :inline => "<%= auto_complete_result @authors, 'name' %>"
end

Hmm, even that I'm not sure will work, but worth a try.

Last edited by ryanb (2006-11-07 12:58:03)

Railscasts - Free Ruby on Rails Screencasts

Re: inserting a different table text box into a form view?

I'd put that method in the Model right? Not the controller. Sorry to say, it doesn't appear to be working. It doesn't return an error, just doesn't do the auto complete (though it is doing SOMETHING, as my browser has now stopped trying to auto complete the field via its own cache).

By the way, I'm not sure I've thanked you yet for all your help. You're pretty amazing.

Re: inserting a different table text box into a form view?

Try it in the controller, that's where it should go.

Railscasts - Free Ruby on Rails Screencasts

Re: inserting a different table text box into a form view?

BLAM! There it is. Fantastic. Thanks again.

Re: inserting a different table text box into a form view?

Fantastic -- thank you both for posting. This solves a newbie problem that's been driving me crazy for the last two days!

Thanks again!

Re: inserting a different table text box into a form view?

OK, next round of newbie muddling.

OK, so we've gotten authors hooked up so I can enter the author information in via the story_admin controller. Awesome. I've also hooked it up so I can list stories and include the author names for each story along with it. Perfect. The entire story_admin section works exactly as I would hope it would.

But (there's always a but, isn't there?)

Now I want to be able to view the authors and have a list of all their stories.

I've created an author_admin controller and the associated views (that way I can store more information as well, like address etc... associated with the authors beyond name). In the LIST view (and SHOW for that matter), I want to not only see a list of the authors but below that see the stories they've written. I.E:

Author one
--story 12
--story 8

Author two
--story 1
--story 7

These are the things I think I know:

1) In the controller, I should have this, right?

  def list
    @author_pages, @authors = paginate :authors, :per_page => 25, :order => 'name'
    @stories = Story.find(:all)
  end

That pulls all the stories and has them available to the list view.

But then I get lost in what to do in the view itself. Here's what I've got, and it's giving me a NoMethodError. I thought it might work because other people were asking similar stuff and this type of code was the answer. Obviously, I'm off--either by a hair or by a mile (this code is shortened a little just for ease of reading)

<table>
    <% for author in @authors %>
    <span class="top-line"><b><%= h(author.name) %></b></span>
<li><%= h author.stories.title %></li>
</p>
<%end%>
</table>

The error is pointing to the author.stories.title line and is saying

"undefined method `title' for Story:Class"

if I DON'T have stories plural (i.e, I'm calling author.story.title--which is actually what I think I should be calling) then I get:

"undefined method `story' for #<Author:0x224dcb0>"

Re: inserting a different table text box into a form view?

You need to loop through the stories for each author (author.stories is an array):

<% for story in author.stories %>
  <li><%=h story.title %></li>
<% end %>

Railscasts - Free Ruby on Rails Screencasts

Re: inserting a different table text box into a form view?

awesome. There it is.