Render javascript from multiple places: Rails path

I have a modal form rendered via javascript. The model is called a book .

# controllers/books_controller.rb def new @book = Book.new end def create @book = Book.find(params[:id]) @book.save end 

Instead of new and html editing, 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 to start the modal:

 = link_to "Create Book", new_book_path, remote: true 

Now the problem is that this link was only used on the book list page. Thus, the js callback, when the book was created, triggered a warning and updated the list with the changes.

Now I have to add this button to another page where there is no list, so I need another callback (no matter what callbacks really are).

So, I had to add something like to create.js.coffee:

 # 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 to be dirty, but it is not so terrible. The problem is that I now have more than three conditional expressions, because the "Create Book" button is used several times along the web application.

So, any ideas on a design pattern for this?

+8
ruby-on-rails coffeescript turbolinks
source share
2 answers

What you do is not scary, but you could clean a few things a bit. I would recommend moving the business logic from both the view and the controller and use Presenter and Helper templates instead. These templates are pretty well documented these days and have a number of advantages, including:

  • skinny controller promotion
  • promotion of smaller, more concise code snippets
  • Promotion of the Law of Demeter
  • simplified testing

Here's a pretty good description of the Presenter template: https://gist.github.com/somebox/5a7ebf56e3236372eec4 or: http://eewang.imtqy.com/blog/2013/09/26/presenting-the-rails-presenter-pattern/

Basically, how it works, you move your business logic into a separate class called a "presenter." This class contains the logic that you usually keep in your controller.

Helpers are also well documented and work the same, but for views. Helpers are much easier to test than logic in views. To get more information: http://api.rubyonrails.org/classes/ActionController/Helpers.html

It might look something like this (note that this is just the unverified "pseudo" code that I use to illustrate the template):

  # 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 

Thus, in this case, business logic can be easily tested in your application using RSpec or any other testing framework that you use. JavaScript complexity is reduced, and testing for it becomes easier. Your JS outputs can be defined separately in different partial files, if you want, or simply return the actual JS from your auxiliary module. This is a complex template that needs to be adopted first, but over time, everyone is likely to be more natural, modular and easy to maintain.

+1
source share

Perhaps you should consider keeping the success / error logic in the controller and instead use separate views based on success / failure. So you have create_success.js.coffee and create_error.js.coffee . Each of them controls his situation and does not care about the other. NOTE: this is psuedo-code.

 # controller def create @book = Book.find(params[:id]) if @book.save # save will run validations render :create_success else render :create_error end end # Views # # create_success.js.coffee CustomModal.hide() # other stuff you do if successful # create_error.js.coffee # re-render form with errors # assuming the modal is already open, you might want to just replace the form, rather than re-open the modal. $(".myFormSelector").html("<%= j render('books/modal_form', book: @book)%>") 
0
source share

All Articles