Search code examples
ruby-on-railsactiverecordrjsactivescaffold

RoR: How to handle custom nested form's submit


I managed to do almost all the way towards happiness with my custom form in ruby-on-rails, but the very last step is missing and it is impossible to find the answer on the net because of too many common words.

I believe that the answers to my questions are trivial for people who have done RoR for a time, but be warned that the presentation of the question will be somewhat complicated.

Let's see an equivalent problem!

Schema:

  • publishers (id, name, address)

  • books (id, title, publisher_id, publishing_year, unit_price, qty)

  • sell_log (id, user_id, timestamp, book_id, qty, unit_price, comment)

Custom action:

  • Name: Sell (context: a book)

  • Input: qty, comment, (implicit input: book.id, timestamp; derived input: user_id, book.unit_price, book.qty)

  • Result:

    • sell_log is appended

    • books.qty decreased

  • Possible errors:

    • The qty is non-positive or non-integer.

    • The qty at the user input is greater than the qty available (book.qty)

(FYI: It is not a question about database design.)

So we have a custom form (hidden book-id; qty, comment) which we want to implement as an action in a similar behavior as "Edit" of a book (update). What is done (is almost everything):

-- books_controller.rb: Added custom_qty_display column.

-- books_helper.rb:

def custom_qty_display_column(record)
  record.qty.to_label + " ["
  link_to( "Sell..." \
            , { :controller => "books", :action => "sell_form", :id => record.id, :page => false } \
            , { :position => "replace", :inline => true, :class => "action" } \
          ) \
  + "]"
end

-- views/books/sell_form.erb (only key details)

<%
  form_remote_tag( \
    :url => { :controller => :books, :action => :sell, :id => params[:id] } \
  ) do
%>
...
<%= submit_tag 'Submit' %>
<%= link_to as_(:cancel), main_path_to_return, :class => 'cancel' %>
<% end %>
<div id="as_books-messages" class="messages-container" />

-- books_controller.rb:

def sell
  errors = [] # We will collect error messages here
  # Checking parameters ...
  # Checking of available qty ...
  # If "errors" is still empty here, perform the action
  # Produce the output according to the above:
  if request.xhr?
    if errors.empty?
      # Q1: rendering of javascript which replaces the form with the modified row in the table.
    else
      # Q2: rendering of javascript which provides the "errors" for the user
    end
  else
    if errors.empty?
      index
    else
      # Q3: Redisplay the form and errors
    end
  end
end

Current progress

When I click the "Sell..." link at a book list entry the entry disappears, custom form appears instead of it. On the form the "Cancel" link (and [X] button) works perfectly; the SUBMIT button works (the action is completed successfully when the input is correct).

What is not there is that the form remains in place. In theory I should return the appropriate javascript on places marked with Q1, Q2 and Q3. I do not want to reverse engineer things and write javascripts with hand because on a framework upgrade I would be forced to redo this step. I want to produce the necessary javascripts in the best possible way regarding simplicity and maintainability. As I believe now my concept is not bad.

Version information

  • JRuby 1.5.0
  • gems
    • rails 2.3.4
    • activerecord 2.3.4
    • activesupport 2.3.4

(Tell me if anything else needed)

Partial result

# ...
if errors.empty?
  render :action => 'on_update.js'
else
  # ...
end
# ...

Solution

  • Step #1: You have to modify the link_to in your helper to include eid

    link_to( "Close..." \
        , { :controller => "books", :action => "sell_form", :id => record.id, :page => false, :eid => @controller.params[:eid] } \
        , { :position => "replace", :inline => true, :class => "action" } \
      )
    

    Step #2: You have to modify the sell_form.erb and set parent_model, parent_column, nested and eid in the url. You have to set the update to book_list, and you have to generate a proper HTML id for the form with element_from_id().

    <%
      form_remote_tag( \
        :url => { :controller => :books, :action => :sell, :id => params[:id], :parent_column => "books", :parent_model => "Publisher", :nested => "true", :eid => params[:eid] } \
        , :update => :book_list \
        , :html => { :id => element_form_id(:action => :update) } \
      ) do
    %>
    

    Step #3: Modify the if request.xhr? part to the following simple code. (Not fully tested, the best case works properly.)

    if request.xhr?
      if @record.valid?
        render :action => 'on_update.js'
      else
        render :action => 'form_messages.js'
      end
    else
      if @record.valid?
        return_to_main
      else
        render :action => 'sell_form'
      end
    end