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 re开发者_StackOverflowgarding 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
# ...
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.
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
精彩评论