从多个位置呈现JavaScript:Rails的方式

8

我有一个通过JavaScript渲染的模态表单,该模型被称为book

# controllers/books_controller.rb

def new
  @book = Book.new
end

def create
  @book = Book.find(params[:id])
  @book.save
end

我使用Coffeescript代替新建和编辑HTML:

# 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 %>

触发模态框的链接:

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

现在,我面临的问题是这个链接仅在书籍列表页面上使用。所以当创建书籍时,js回调会触发警报并更新列表以反映更改。
现在我必须在另一个没有列表的页面中添加这个按钮,因此我需要一个不同的回调(真正的回调无所谓)。
因此,我必须在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 %>

看起来有点不干净,但其实并没有那么糟糕。问题在于我现在有超过3个条件语句,因为“创建图书”按钮在Web应用程序中被多次使用。

那么,对于这个问题,您有什么设计模式的想法吗?


我喜欢返回Coffeescript的方法。它有一种很好的感觉,有点静态动态。当你询问“关于这个设计模式的想法”时,你在寻找答案中具体需要什么? - Michael Gaskill
我正在寻找一种模式,使得所有不同的可能性都易于维护等。 - ascherman
不如不返回实际脚本,而是调用回调函数,在需要链接的每个页面上可以定义不同的回调函数。类似这样:如果定义了回调函数,则调用回调函数 否则执行默认操作 - michalvalasek
2个回答

1
你所做的不是很糟糕,但你可以采取一些措施来使其更加清晰。我建议将业务逻辑从视图和控制器中移出,并使用Presenter模式和Helper模式。这些模式现在已经有了相当多的文档说明,并具有以下几个好处:
  • 促进控制器的精简
  • 促进更小、更简洁的代码片段
  • 促进迪米特法则
  • 使单元测试更容易
这里有一个关于Presenter模式的很好的描述: https://gist.github.com/somebox/5a7ebf56e3236372eec4 或者: http://eewang.github.io/blog/2013/09/26/presenting-the-rails-presenter-pattern/ 基本上,它的工作原理是将业务逻辑移到一个名为“presenter”的单独类中。该类保存通常会保留在控制器中的逻辑。

Helpers同样有很好的文档,并且与逻辑代码类似,但是主要用于视图。相比于视图中的逻辑代码,Helpers更容易进行测试。更多信息请参见: http://api.rubyonrails.org/classes/ActionController/Helpers.html

代码示例如下(请注意,这只是未经测试的“伪”代码,用于说明该模式):

    # 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

在这种情况下,业务逻辑可以很容易地通过RSpec或您正在使用的任何测试框架在应用程序中进行单元测试。 JavaScript的复杂性得到了减少,并且对它的测试变得更加简单。 如果您喜欢,您的JS输出可以在不同的部分中单独定义,或者只需从您的帮助器模块返回实际的JS。 这是一种复杂的模式,但随着时间的推移,一切都可能会感觉更加自然,模块化和易于维护。

0

你可能想考虑将成功/错误逻辑保留在控制器中,而是根据成功/失败使用单独的视图。因此,你可以有一个create_success.js.coffee和一个create_error.js.coffee。每个只处理自己的情况,不关心其他情况。注意:这是伪代码。

# 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)%>")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接