views:

120

answers:

2

Hello!

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
# ...
+1  A: 

Which Rails version is the app using? What javascript library is the app using? Are Book and SellLog RESTful resources?

Is their a reason you're not using link_to_remote in the helper and respond_to blocks in the controller actions?

With Rails 2.3 using prototype I do it like this:

in controller:

def sell 
  # …
  # book / quantity / errors
  # …
  respond_to do |format|
    if errors.empty?
      format.js {render :update do |page|
        page.replace_html '_ID_OF_DOM_OBJECT_CONTAINING_ROW_INFO_', :partial => '_PARTIAL_FOR_ROW_INFO_LAYOUT_' 
      end}
      format.html { redirect_to :action => _WHATEVER_THE_SUCCESS_ACTION_IS_ }
    else
      format.js {render :update do |page|
        page.replace_html 'form', :partial => '_DOM_ID_OF_FORM_' 
      end}
      format.html { render :action => _WHATEVER_ACTION_DISPLAYED_THE_FORM_ }
    end
  end
end

_PARTIAL_FOR_ROW_INFO_LAYOUT_ would have:

<div id="_ID_OF_DOM_OBJECT_CONTAINING_ROW_INFO_">
  <!-- row display markup -->
</div>

A lot of what you're doing would be easier when following the Rails conventions, but I don't know your Rails version, app DSL, or what plugins and/or gems your app uses that would explain the current pattern in your controller.

inkdeep
I added version information at the end of my question.`_PARTIAL_FOR_ROW_INFO_LAYOUT_` should have some trivial way to render. If I debug the response of a successful "Edit" (`update`) action I see tons of javascript. I hoped that someone knows the few lines needed to generate those. I continue digging, will post my own results when I have it.Almost forgot! Your response still teaches a lot! Thanks :-)
Notinlist
A: 

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
Notinlist