I have a modal form rendered through javascript. The model is called book.
# controllers/books_controller.rb
def new
@book = Book.new
end
def create
@book = Book.find(params[:id])
@book.save
end
Instead of having the new and edit html, I use coffeescript:
# views/new.js.coffee
CustomModal.open "<%= j render('books/modal_form', book: @book) %>"
-
# views/create.js.coffee
<% if @book.valid? %>
CustomModal.hide()
# Other callback scripts for showing alert, etc
<% else %>
# Script for showing errors in the modal
<% end %>
And the link for triggering the modal:
= link_to "Create Book", new_book_path, remote: true
Now, the problem I face is that this link was used just on the book's list page. So the js callback when the book was created, triggered an alert and updated the list with the changes.
Now I have to add this button in another page, where there is no list, so I need a different callback (doesn't matter which callbacks really).
So, I had to add to the create.js.coffee something like:
# views/create.js.coffee
<% if @book.valid? %>
CustomModal.hide()
# if the list exists
# show alert
# update lists
# else
# do different things
# end
<% else %>
# Script for showing errors in the modal
<% end %>
It seems kind of dirty, but it's not so awful. The problem is that I have more than 3 conditionals now, because the "Create Book" button is used multiple times along the webapp.
So, any ideas about a design pattern for this?
What you're doing isn't terrible but you could a couple of things to clean it up a bit. I would recommend moving the business logic out of both the view and the controller and using the Presenter pattern and Helper patterns, instead. These patterns are pretty well documented these days and have a number of benefits including:
Here's a pretty good description of the Presenter pattern: https://gist.github.com/somebox/5a7ebf56e3236372eec4 or: http://eewang.github.io/blog/2013/09/26/presenting-the-rails-presenter-pattern/
Basically, the way it works is that you move your business logic into a separate Class called a 'presenter'. This class holds the logic that you would normally keep in your controller.
Helpers are also pretty well documented and work much the same way, but for views. Helpers are much easier to test than logic in views. For more information: http://api.rubyonrails.org/classes/ActionController/Helpers.html
It might look something like this (please note that this is just untested, 'pseudo' code that I'm using to illustrate the pattern):
# app/controllers/books_controller.rb
helper BooksHelper
def create
book = Book.find(params[:id])
book.save
@presenter = BookPresenter(book)
end
# app/presenters/book_presenter.rb
# move your 'fat' controller logic here
class BookPresenter
attr_reader :book, :page_type
def initialize(book, options={})
@book = book
end
private
def page_type
# custom code here for determining page type
end
...
end
# app/helpers/books_helper.rb
# move your view logic here
module BooksHelper
def custom_modal(book_presenter)
if book_presenter.book.is_valid
handle_valid_book(book_presenter)
else
# handle invalid book
end
end
def handle_valid_book(book_presenter)
custom_list_modal if book_presenter.page_type == 'has_list'
custom_listless_modal if book_presenter.page_type == 'listless'
# other conditions
end
def custom_list_modal
# modularized JavaScript for pages with a list
end
def custom_listless_modal
# modularized JavaScript for pages without a list
end
...
end
So in this case, the business logic can be easily unit-tested in your application using RSpec or whatever testing framework you are using. JavaScript complexity is reduced and testing for it becomes simpler. Your JS outputs can be defined separately in different partials if you like, or just return the actual JS from your helper module. It's a complex pattern to adopt at first, but over time everything will likely feel more natural, modularized, and easy to maintain.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With