views:

67

answers:

4

Ok, I have a model that is very simple:

ServiceType(id: integer, title: string, duration: integer, created_at: datetime, updated_at: datetime)

Because its so simple, It's ridiculous that I should have a separate page for creating and updating those types of records. What I would like to do is have a form on my index page which I can use to create new records and if I click the edit link, I would like the form to load that record so that the form doubles as a create/update form.

Has anyone already done this before or does anyone know of a blog article somewhere that shows how to do this?

EDIT: Using the blog link posted below by Laheab I am at this point: If you are using Rails 3, Laheab's link is what you want, but BE WARNED!! The guy who wrote it left a ton of obvious errors in his code.. look for bad comment blocks like */ */ instead of /* */ and look for times where he is using jQuery but leaves off the $.

IF you are using Rails 2.3.x, I have posted my modified version of this as an answer.

+1  A: 

It's simple to put your "new service type" form at the top of the index. It's the same as if it were in the "new" template.

To edit the existing records, you might consider something like in-place editing, which used to be a part of rails core but is now a plugin.

Andrew Vit
+1 for a simple plug-in but the link that Laheab posted is really close to what I'm looking for. Maybe you can look at my edit above and try to figure out where I'm going wrong? With out a `respond_to` block, I'm getting an error but I don't know what to put there instead. Check out the `posts_controller.rb` file.
DJTripleThreat
Andrew Vit
A: 

First of all I would write the controller for this model in an RESTfull manner and write appropriate view templates using partials. Then I would render the form partials on the index page.Probably using AJAX will ease things.
This topic is related to components etc. and AFAIK the Rails way to components are partials, but making the controllers play nice is not an easy task. Read:

Third, Rails' "controller first" dispatch mechanism makes the assumption that there is only one piece of "logic" on the page and the rest is decoration. My experience doing web work is just the opposite. There are typically 3 or more of pieces of logic on a page (dynamic menu bars, search boxes, shopping cart, real-time chat, etc.) and having to choose which piece of logic make the "controller" is less than optimal.
David Pollak

Source: http://wiki.github.com/dpp/liftweb/about-view-first

I'm curious how uberpro Rails devs deal wit those issues.

Jeznet
Sorry, but this answer is off-topic. The original question doesn't indicate whether or not the controllers are written for REST, and there's no reason to think they aren't, since Rails defaults are restful.A static partial is all that's needed for rendering the "new" form, there's no need for anything like components.It's not helpful to discuss or compare Scala Lift against Rails in this discussion. You should open your own question for that if you wish.
Andrew Vit
First of all I haven't compared Rails to Lift. I only gave a quote with an opinion about Rails. A quote which in my opinion demonstrates a possible problem. About REST - my intent was to gave a clear description of a possible way to solve the problem (question). I usually write more to be well understood.Your opinion about partials, components is Your opinion. Mine is a bit different :-)
Jeznet
Hi Jeznet, thanks for your input but I don't think that is where I'm going with this. Look at the link that Laheab posted and my edit to this post. I'm almost there, I'm just having an issue with how I should deal with `respond_to`.
DJTripleThreat
+2  A: 

Something like this Creating a 100% ajax CRUD using rails 3 and unobtrusive javascript?

Laheab
+1 this is totally what I'm looking for but its for rails 3. I have some `respond_to` issues, maybe you can look at my edit and see where I'm going wrong.
DJTripleThreat
Sadly I don't have a Rails 2 version so I'm incapable of testing this.. I'll check and edit again.
Laheab
Ok, most of the problems were actually from the blog post itself. That guy was super sloppy. If you go to his blog, look at all of his mistakes (doing `*/ */` instead of `/* */` and leaving off `$` when accessing jQuery) which caused most of my problems. The ruby issue was that I needed `format.js { render :layout => false }`
DJTripleThreat
Hope things work now. =)
Laheab
@Laheab. Not yet :( I've got most of it working, but the delete and update actions are rendering the javascript as html. I think it has something to do with those actions using the PUT and DELETE HTTP methods. I have the problem tracked down and I reposted my question (see: http://stackoverflow.com/questions/3595014/javascript-not-being-rendered-at-script-but-as-html-instead)
DJTripleThreat
+1  A: 

If you are running Rails 2.3.x, you need to modify what the guy does in this blog link.

This is what I did to get create/edit/destroy(this isn't working yet, it still renders destroy.js.erb as html for some reason) it to work:

make rails app:

$ rails post_app
$ cd post_app
$ script/generate scaffold Post title:string content:text
$ rake db:migrate

And now add/replace the following files:

public/javascripts/application.js (see Railscasts episode 136 http://railscasts.com/episodes/136-jquery):

// Setting up ajax for sending javascript requests
jQuery.ajaxSetup({
  'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
});

jQuery.fn.submitWithAjax = function() {
  this.submit(function() {
    $.post(this.action, $(this).serialize(), null, "script");
    return false;
  });
  return this;
};

jQuery.fn.getWithAjax = function() {
  this.click(function() {
    $.get(this.href, null, null, "script");
    return false;
  });
  return this;
};

jQuery.fn.postWithAjax = function() {
  this.click(function() {
    $.post(this.href, null, null, "script");
    return false;
  });
  return this;
};

app/controllers/posts_controller:

class PostsController < ApplicationController
  before_filter :load

  def load
    @posts = Post.all
    @post = Post.new
  end

  def index
  end

  def edit
    @post = Post.find(params[:id])
    respond_to do |format|
      format.all {render :file => "#{RAILS_ROOT}/public/404.html", :status => '404 Not Found'}
      format.js {render :layout => false}
    end
  end

  def create
    @post = Post.new(params[:post])

    if @post.save
      flash[:notice] = 'Post was successfully created.'
      @posts = Post.all
    end

    respond_to do |format|
      format.js {render :layout => false }
    end
  end

  def update
    @post = Post.find(params[:id])

    if @post.update_attributes(params[:post])
      flash[:notice] = 'Post was successfully updated.'
      @posts = Post.all
    end
    respond_to do |format|
      format.js {render :layout => false }
    end
  end

  def destroy
    @post = Post.find(params[:id])
    @post.destroy
    flash[:notice] = "Successfully destroyed post."
    @posts = Post.all
    respond_to do |format|
      format.js {render :layout => false }
    end
  end
end

app/views/layouts/posts.html.erb

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Posts: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= javascript_include_tag 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js', 'application' %>
  <%= javascript_include_tag 'rails' %>
  <%= yield :head %>
</head>
<body>
  <div id="container">
    <div id="flash_notice" style="display: none; color: green"></div>
    <%= yield %>
  </div>
</body>
</html>

under app/views/posts delete EVERYTHING and add the following files:

index.html.erb:

<%- content_for :head do -%>
  <%= javascript_include_tag '/posts.js' %>
<% end %>
<h1>Listing posts</h1>
<%= render :partial => "form" %>
<div id="posts_list">
<%= render :partial => "posts" %>
</div>

index.js.erb:

// Setting up the ajax requests for the forms/links.
$(document).ready(function() {
  $("#new_post").submitWithAjax();
  $("a.edit").each(function(){
    $(this).getWithAjax();
  });
  $("a.destroy").each(function(){
    $(this).postWithAjax();
  });
});

create.js.erb:

<% if @post.errors.any? -%>
  /*Hide the flash notice div*/
  $("#flash_notice").hide(300);

  /*Update the html of the div post_errors with the new one*/
  $("#post_errors").html("<%= escape_javascript(error_messages_for(@post))%>");

  /*Show the div post_errors*/
  $("#post_errors").show(300);
<% else -%>
  /*Hide the div post_errors*/
  $("#post_errors").hide(300);

  /*Update the html of the div flash_notice with the new one*/
  $("#flash_notice").html("<%= escape_javascript(flash[:notice])%>");

  /*Show the flash_notice div*/
  $("#flash_notice").show(300);

  /*Clear the entire form*/
  $(":input:not(input[type=submit])").val("");

  /*Replace the html of the div post_lists with the updated new one*/
  $("#posts_list").html("<%= escape_javascript( render(:partial => "posts") ) %>");
<% end -%>

destroy.js.erb:

$("#post_errors").hide(300);
$("#flash_notice").html("<%= escape_javascript(flash[:notice])%>");
$("#flash_notice").show(300);
$("#posts_list").html("<%= escape_javascript( render(:partial => "posts") ) %>");

_posts.erb:

<table>
  <tr>
    <th>Title</th>
    <th>Content</th>
  </tr>
  <% for post in @posts %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.content %></td>
      <td><%= link_to "Edit", edit_post_path(post), :class => "edit" %></td>
      <td><%= link_to "Destroy", post, :confirm => 'Are you sure?', :method => :delete, :class => 'destroy' %></td>
    </tr>
  <% end %>
</table>

edit.js.erb:

$("#new_post").html("<%= escape_javascript(render(:partial => "form"))%>");
$("#post_title").val('<%= escape_javascript(@post.title)%>');
$("#post_content").val('<%= escape_javascript(@post.content)%>');

_form.erb:

<% form_for @post do |f| %>
  <div id="post_errors" style="display:none"></div>
  <p>
    <%= f.label :title %><br/>
    <%= f.text_field :title %>
  </p>
  <p>
    <%= f.label :content %><br/>
    <%= f.text_area :content, :rows => 5 %>
  </p>
  <p><%= f.submit %></p>
<% end %>

update.js.erb:

<% if @post.errors.any? -%>
  $("#flash_notice").hide(300);
  $("#post_errors").html("<%= escape_javascript(error_messages_for(@post))%>");
  $("#post_errors").show(300);
<% else -%>
  $("#post_errors").hide(300);
  $("#flash_notice").html("<%= escape_javascript(flash[:notice])%>");
  $("#flash_notice").show(300);
  $(":input:not(input[type=submit])").val("");
  $("#posts_list").html("<%= escape_javascript( render(:partial => "posts") ) %>");
<% end -%>
DJTripleThreat