Topic: Need some rails/database advice

Okay, so I'm working on this site called The Daily Translation where each day people get to try and figure out 3 different phrases.

There is one form on the site for people input their guesses that is setup something like this as far as fields go:

Name:
Email:
1st phrase guess:
2nd phrase guess:
3rd phrase guess:

So, two fold question here.

1) How would you setup the tables for this? One table for the name/email side of things and then another table where each guess would get put in it's own row and be attached to the name/email and phrase it pertains to?

2) How would I loop through each of those guesses to put them in their own row? smile

Re: Need some rails/database advice

You probably want two models/tables here: User and Guess. In this case, User has_many :guesses and Guess belongs_to user and phrase. The tricky part is to create a form that allows you to submit multiple models. First we need to build the defaults in the controller. This will create a new User with three guesses (assuming the phrase_id you want is given in the params):

def new
  @user = User.new
  3.times do { @user.guesses.build(:phrase_id => params[:phrase_id]) }
end

Next we need to make a form that handles these multiple models. We'll use the fields_for helper here.

<%= start_form_tag :action => 'create' %>
<p>Name: <%= text_field :user, :name %></p>
<p>Email: <%= text_field :user, :email %></p>
<% @user.guesses.each_with_index do |guess, index| %>
  <% fields_for "guesses[#{index}]", guess do |f| %>
    <%= f.hidden_field :phrase_id %>
    <p>
      <%= index.ordinalize %> phrase guess:
      <%= f.text_field :content %>
    </p>
  <% end %>
<% end %>
<%= end_form_tag %>

When the form is submitted, it creates a hash at params[:guesses] which we can loop through to generate the guesses. Like this:

def create
  @user = User.new(params[:user])
  params[:guesses].each_value { |guess| @user.guesses.build(guess) }
  if @user.save
    redirect_to :action => 'index'
  else
    render :action => 'new'
  end
end

I didn't add the validation error messages to the form, but you'll probably want to.

I will be creating a tutorial on this subject which will go further in depth and explain a few things a bit more. You may want to keep an eye out for that.

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

That second chunk of code that builds the form throws out a nil object error.

I'm totally following what that code does but here's what I currently have that creates the form how I'd like it. It just doesn't setup the names like it should (I don't think).

<% for phrase in @phrase %>
    <div>
        <label><%= phrase.translation %></label>
        <%= text_field 'phrases', 'translation'  %>
    </div>
<% end %>

Re: Need some rails/database advice

First of all, how are you generating the @phrase instance variable? You are using it like it's a collection/array, but it's not plural.

Second, the phrase in the loop needs to be made an instance variable so the text_field helper can find it.

Third, the text field helper should specify 'phrase', not 'phrases'.

With those fixes, the code should look something like this:

<% for @phrase in @phrases %>
  <div>
    <label><%= @phrase.translation %></label>
    <%= text_field 'phrase', 'translation'  %>
  </div>
<% end %>

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

Here's how I'm getting the @phrase:

def index
   @phrase = Phrase.find(:all,
                        :conditions => "date = now()",
                        :order      =>  "id")
end

It generates the form fields exactly how I want...it's just submitting the form and getting it to process said form fields that isn't turning out so hot.

Re: Need some rails/database advice

Have you tried renaming that to @phrases and using the code I gave above? It should be plural because that variable is holding more than one phrase.

Shpigford wrote:

It generates the form fields exactly how I want...it's just submitting the form and getting it to process said form fields that isn't turning out so hot.

The reason it is so difficult is you are using multiple models in one form. The text_field helper will give all the text fields the same name, but each text field for each phrase needs a different name so it is sent separately. See this article for some information on editing multiple models in one form.

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

Changing to @phrases and using the code worked fine. I read the article but am still not following. The form is building fine but is giving the text_field name="phrase[translation]" which i figure it needs to be like phrase[translation][0] or something. Or am I totally missing this?

Re: Need some rails/database advice

Shpigford wrote:

The form is building fine but is giving the text_field name="phrase[translation]" which i figure it needs to be like phrase[translation][0] or something. Or am I totally missing this?

You are on the right track. The text field name must be unique for it to be sent separate from the other text fields, so you need to append the id of the phrase to the name attribute to make it unique.

Are you using the text field to edit the "translation" attribute of the phrases? Or are you using it to create new "guess" models of some kind? Sorry if I'm confusing, I'm just not exactly sure what you are trying to do.

Last edited by ryanb (2006-10-05 19:11:43)

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

I'm trying to create new guesses.

So like say there are 3 phrases posted for the day that people must translate.

They are given a form to input the following:


Name: <input>
Email: <input>
This is phrase 1: <input>
This is phrase 2: <input>
This is phrase 3: <input>
<submit>

And in the <input>'s for those 3 phrases they are filling in what they guess the translation of those phrases would be.

So what the use types in the text fields is their answer (or "translation") and that gets submitted.

Re: Need some rails/database advice

Sorry I never got back to you on this. Somehow I missed your reply. Here's how I would do it.

Create three tables, phrases, users, and guesses. In this case, User has_many :guesses and Guess belongs_to both :user and :phrase.

If I understand you correctly, you want to take, say, three phrases and have each user make a guess for each phrase. In the "new" controller action, create the default User model with blank Guesses, one for each phrase you want to guess. Like this:

def new
  @user = User.new

  # Fetch the phrases however you like here:
  Phrase.find(:all).each do |phrase|
    @user.guesses.build(:phrase => phrase)
  end
end


Now that you have the empty User and guesses, display them in the form similar to how I mentioned in the other post:

<%= start_form_tag :action => 'create' %>
<p>Name: <%= text_field :user, :name %></p>
<p>Email: <%= text_field :user, :email %></p>
<% @user.guesses.each_with_index do |guess, index| %>
  <% fields_for "guesses[#{index}]", guess do |f| %>
    <%= f.hidden_field :phrase_id %>
    <p>
      <%= guess.phrase.description %>
      <%= f.text_field :content %>
    </p>
  <% end %>
<% end %>
<%= end_form_tag %>

If this code is generating an error as you mentioned before, let me know what the error is and I may be able to help.

Saving the submission to the database is the same as I mentioned before:

ef create
  @user = User.new(params[:user])
  params[:guesses].each_value { |guess| @user.guesses.build(guess) }
  if @user.save
    redirect_to :action => 'index'
  else
    render :action => 'new'
  end
end

Hope that works for you. Sorry I haven't been much help so far on this.

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

Hey Ryan, I'm still getting an error.

It's happening on this line:

<% @user.guesses.each_with_index do |guess, index| %>

I'm assuming that pulls something from the users table in the database. So my fault for not clarifying there, but there's not an actual "user" until they make a guess. Think of it like a blog where people comment on. They don't have to register or anything, they just drop in their name and email. And then, in this case, make some guesses.

So, in my mind there wouldn't be a reason to pull anything from the users table. Or am I just completely off there?

And thanks for your help thus far. Me not figuring this out is totally due to the fact that I'm so new to ruby/rails. I'm getting there though smile

Re: Need some rails/database advice

Shpigford wrote:

Hey Ryan, I'm still getting an error.

It's happening on this line:

<% @user.guesses.each_with_index do |guess, index| %>

I'm assuming that pulls something from the users table in the database. So my fault for not clarifying there, but there's not an actual "user" until they make a guess. Think of it like a blog where people comment on. They don't have to register or anything, they just drop in their name and email. And then, in this case, make some guesses.

So, in my mind there wouldn't be a reason to pull anything from the users table. Or am I just completely off there?

Actually it's not pulling anything from the database. Instead it should be using the @user instance variable that is created in the "new" controller action. Are you placing this form in the "new.rhtml" file? If so, the "new" controller action should be run before this is called and generate an empty @user model with empty guesses. This is just for setting up a default for the form to be generated from. After this form is submitted the models are recreated based on the values of the form and then they are saved to the database.

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

ryanb wrote:

Actually it's not pulling anything from the database. Instead it should be using the @user instance variable that is created in the "new" controller action. Are you placing this form in the "new.rhtml" file? If so, the "new" controller action should be run before this is called and generate an empty @user model with empty guesses. This is just for setting up a default for the form to be generated from. After this form is submitted the models are recreated based on the values of the form and then they are saved to the database.

Ah, I've actually just got an index.rhtml file that has all of this on one page. The user wouldn't click "guess" and be taken to a new page or anything. It's all done in one page.

That being the case, I dropped what was in the 'new' action into the 'index' action so that I've got:

  def index
    @user = User.new
   
    # Fetch the phrases however you like here:
    Phrase.find(:all).each do |phrase|
      @user.guesses.build(:phrase => phrase)
    end
  end

Now I'm getting this error:
undefined method `phrase=' for #<Guess:0x2595964>

We're getting there. smile

Re: Need some rails/database advice

The guess model should look something like this:

class Guess < ActiveRecord::Base
  belongs_to :user
  belongs_to :phrase
end

And I'm assuming there's a phrase_id column in the guesses table. Is that how you want it set up? So each guess has its own phrase that it is associated with.

Railscasts - Free Ruby on Rails Screencasts

Re: Need some rails/database advice

SCORE!

I was doing belongs_to :user, :phrase instead of 2 separate ones.

And it's working perfectly now!

Thanks so much for your help Ryan!

Re: Need some rails/database advice

ryanb wrote:

Sorry I never got back to you on this. Somehow I missed your reply. Here's how I would do it.

*snip*

Thanks a billion - this was exactly what I was looking for.  This worked basically as is for getting a simply_helpful form_for and has_many :through to play nice together, if anyone is interested.