Topic: Railscasts

Hi,

I have been learning rails for a while but I have only started on a real project last week. So far everything as been going great except for multi-model forms.

I learned loads of things from Ryan Bates tutorials, but his 3 forms screencast + other readings where not enough to make me understand how_forms_work in rails. I have read about presenter pattern, but my problem is not solved with that because of messy db relationships.

Can someone just help me and direct me to a learning resource where I can rapidly solve this. The project is due on Monday and there's still a lot of web design (css/html) to be made, so the code should be finished by now sad

So that you guys can understand the problem a little better this is what I have now. Basically a user can launch a Assignment and other users can Group up (users are only allowed to have one group) and submit a response (Submission) to that project. A Submission in a bunch of files grouped by Container (containers declare the file type and are specific to each Assignment: ex. 'source code', 'report', 'presentation')

[code=Group.rb]class Group < ActiveRecord::Base
  has_many :users
  has_many :submissions
  #(...)
end[/code]
[code=User.rb]class User < ActiveRecord::Base
  belongs_to :group
  has_many :assignments
  #(...)
end[/code]
[code=Assignment.rb]class Assignment < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many :containers
  has_many :submissions
  #(...)
end[/code]
[code=Submission.rb]class Submission < ActiveRecord::Base
  belongs_to :assignment
  belongs_to :group
  has_many :submission_files
  #(...)
end[/code]
[code=Container.rb]class Container < ActiveRecord::Base
  has_and_belongs_to_many :assignments
  has_many :submission_files
  #(...)
end[/code]
[code=SubmissionFile.rb]class SubmissionFile < ActiveRecord::Base
  belongs_to :container
  belongs_to :submission

  ## attachment_fu, multipart html?
  has_attachment :storage => :file_system,
                 :max_size => 500.kilobytes

  validates_as_attachment
end[/code]
Given all this all I'm trying to do is a new form for users to submit their responses (submissions) to the assignment. First they select the assignment and press go. Then a form would ask for files as group id is a given

[code=Submission_Controller.rb]
  #(...)
  submission.group.id = User.find_by_id(session[:user]).group_id
  #(...)
[/code]

For each container in that assignment users could add files to that container. The problem is that attachment_fu requires multipart, so i suppose i have to call the submission.file.new for each file added to container. Then another problem is I'm unsure about the database model. I know it's ok but is it ok for rails? It this the easiest way to do it? Is has_many_through an possible answer?!

If you can just give me a place to learn about this I would be very grateful.

Thanks

Last edited by dscape (2008-02-09 13:33:33)

Re: Railscasts

My Ever So Lame response to my own problem. I figured it out myself, so yes, this is possible the best example you can get on newbie errors tongue

I started with the Submission_Controller, index action

[code=Submissions_Controller.rb]def index
  # by default it renders available assignments
  @render_assignments = true
  # the group of our user
  group = User.find_by_id(session[:user]).group
  # find the groups submissions
  @submissions = Submission.find(:all, :conditions => {:group_id => group.id})
  # get a list of already submitted projects. you can't submit twice. use edit.
  already_submitted_assignments = group.project_entries.map {|e| e.assignment_id}
  # fetch available assignments. exclude outdated and those that the groups already answered to.
  @available_assignments = Project.find(:all,
  :conditions => ['deadline >= ? and id not in (?)', Time.now, already_submitted_assignments])
  # can't render if there's nothing to render
  @render_projects = false if @available_projects == []
end[/code]

With the following index.html.erb
[code=index.html.erb for Submissions]
<h1>Submissions</h1>
<% if @render_projects %>
  <% form_for :submission,
     :url => {:controller => 'submissions', :action => 'submit'} do |f| %>
    <%= f.collection_select(:assigment_id , @available_assignments, "id", "title") %>
    <%= submit_tag "Submit" %>
  <% end %>
<% end %>

<table>
<% for submission in @submissions %>
  <tr>
    <td>
      <%= link_to 'Show', submission %>
    </td>
    <td>
      <%= link_to 'Edit', submission_path(submission) %>
    </td>
    <td>
      <%= link_to 'Destroy', submission, :confirm => 'Are you sure?',
            :method => :delete %>
    </td>
  </tr>
<% end %>
</table>[/code]

If a assignment is available and the user decides to file a submission we will be taken to the submit action.

[code=Submissions_Controller.rb]def submit
  # first we create a new submission
  @s = Submission.new
  # we fetch the assignment id from params.
  @s.assignment_id = params[:submission][:assignment_id].to_i
  # we set the correct group_id
  @s.group_id = User.find_by_id(session[:user]).group_id
  # this are the containers as defined by who made the assignment
  @containers = @s.assignment.containers
  # save the submission
  @s.save
end[/code]

The corresponding submit.html.erb:

[code=submit.html.erb for Submissions]<h1>New Submission</h1>
<%= render :partial => 'fields' %>
<%= link_to 'Back', submissions_path %>[/code]

And fields partial. (It's shared with edit)

[code=_fields.html.erb for Submissions]<h2>Add Files</h2>
<% for container in @containers %>
<div style="padding:0;margin:0 0 0.6em 0;background:url(<%= container.image_path %>)
  no-repeat left top;" id="files">
<label><%= container.name%></label>
<% if @files %>
<!-- The following line selects files for specific container -->
<%= render :partial => 'file',
:collection => @files.reject {|fc| fc.container_id != container.id} %>
<% end %>
</div>
<%= link_to "Add a new file", :controller => 'submission_files',
:action => 'new_file', :container_id => container.id, :project_entry_id =>  @s.id %>
<% end %>[/code]

File partial
[code=_file.html.erb for Submissions]<%= file.filename %><br/>[/code]

Now we go to submission_files_controller to create our new file action.


[code=Submission_Files_Controller.rb]def new_file
  @file = FileContainer.new
  @file.submission_id = params[:submission_id].to_i
  @file.container_id = params[:container_id].to_i
end[/code]

and the new file view:

[code=new_file.html.erb for SubmissionFile]<h1>New file for <%= @file.submission_id %></h1>

<%= error_messages_for :submission_files %>

<% form_for :submission_file, :url => {:action => 'create_file'},
:html => { :multipart => true } do |f| %>
  <%= render :partial => 'fields', :locals => { :f => f } %>
  <p><%= submit_tag "Add File" %></p>
<% end %>[/code]

And the fields partial

[code=_fields.html.erb for SubmissionFiles]<p>
  <label for="file_container">Upload A file_container:</label>
  <%= f.file_field :uploaded_data %>
  <%= f.hidden_field :submission_id , :default => @file.submission_id %>
  <%= f.hidden_field :container_id , :default => @file.container_id %>
</p>[/code]

Now we miss the trivial create_file action

[code=Submission_Files_Controller.rb]def create_file
  @file = SubmissionFile.new(params[:submission_file])
  if @file.save
    flash[:notice] = "File saved."
  else
    flash[:error] = "Couldn't save the file."
end
redirect_to :controller => 'submissions', :action => 'continue_submit',
:submission_id => @file.submission_id
end[/code]

And finally the repeated code. But the code is so messy i didn't took the time to follow DRY.

[code=Submissions_Controller.rb]def continue_submit
    @s = Submission.find_by_id(params[:submission_id])
    @project = Project.find_by_id(@s.project_id)
    if(@project.deadline < Time.now)
      flash[:error] = 'Time is over. You can't edit this item'
      redirect_to :submissions_path
    else
      @containers = @project.containers
      @files = @s.submissionfiles
    end
end[/code]

Last edited by dscape (2008-02-09 20:57:58)