【问题标题】:Rails MVC - Should database search logic go in the model or the controllerRails MVC - 数据库搜索逻辑应该进入模型还是控制器
【发布时间】:2016-07-31 07:44:38
【问题描述】:

我想确保我的代码按照以下范例/模式正确组织/设计:

- 模型视图控制器

- Rails 约定优于配置

- 瘦控制器,胖模型

我按照我认为最重要的顺序在这里列出了它们。


我的问题

在阅读了几篇文章后,尤其是 this onethis one too,我开始将控制器中的一些逻辑转移到我的模型中。

但是,我无法决定是否将我的搜索逻辑(稍后将深入解释)从控制器移动到模型,即使在阅读了更多类似以下的帖子/文章后:MVC ThinkingModel vs. ControllerSeparating concerns 以及更多未在此处列出。


场景

查看

两页:

  • 1 页包含一个文本字段和提交按钮,该按钮将用户的输入作为参数在 POST 请求中作为参数发送到第二页。

  • 第二个页面简单地呈现给定数组中的每个 neatObject,我们称之为 @coolList

控制器

  • 当第二个页面收到 POST 请求时,将调用一个方法,我们称之为 awesomeSearch。 (感谢 Rails 路由)
  • awesomeSearch 应该接受用户的输入,以 params[:searchString] 的形式提供,并与 NeatObject 模型一起构建 @coolList 并使该列表可用于视图呈现。

型号

  • NeatObject 模型处理来自控制器的请求并将 neatObjects 返回给这些控制器。

  • NeatObject 模型定义了 neatObjects 与我们数据库中的其他表之间的关系。

数据库

根据我们的数据库,这些是构成每个 neatObject 的属性:

  • id - int
  • 描述 - 字符串
  • 地址 - 字符串
  • date_created - 时间戳

缺少什么?

控制器如何与模型协同工作以匹配用户输入。

这是我感到困惑的部分。逻辑本身很简单,但我不确定哪些部分属于模型,哪些部分属于控制器。

  • 控制器是否应该将搜索字符串传递给模型,模型将结果传回?

  • 控制器是否应该向模型询问 allneatObjects,然后只保留匹配的那些?

  • 解决方案是两者兼而有之吗?

为了能够就特定逻辑位提出问题,接下来我将更详细地概述搜索过程。


深度搜索逻辑

该过程涉及找到 匹配 搜索字符串的 neatObjects。如果不为 neatObjects 定义我们认为的 matches,就不可能继续前进。为了简单起见,我们会这样说:

如果搜索字符串包含在其描述或其地址中,则整洁对象 匹配搜索字符串,忽略大小写和前导/尾随空格。

不保证此定义是永久的。有几件事可能会改变我们的定义。我们可能需要测试更多的属性,而不仅仅是地址和描述,也许如果数据库人员添加了一个新的重要属性,或者 UI 人员决定用户应该能够通过 ID 进行搜索。当然,与这些场景相反的情况意味着我们需要从我们正在测试的属性列表中删除一个属性。有很多情况可能会改变我们对匹配的定义。我们甚至可能不得不添加或删除逻辑,也许如果我们决定只测试描述属性中的第一个单词,或者我们不应该再忽略大小写。

现在我们知道什么定义了匹配,并且我们知道我们的定义可能会改变。现在我们可以更具体地定义搜索过程了。

以下是步骤概要:

  1. 获取对 all neatObjects 的引用
  2. 遍历每个 neatObject,将每个单独的对象通过 match 测试
    1. 测试通过 - 在结果列表中添加/保留 neatObject
    2. 测试失败 - 不要为结果保留 neatObject
  3. 使结果可用于视图

在展示可能的实现时,我将参考这些步骤。


实施

搜索功能可以很容易地在 NeatObject 模型或为视图提供服务的控制器中实现。

通常情况下,我会在控制器中编写所有逻辑,但是当我了解“瘦控制器,胖模型”设计后,我认为它肯定适用于这种情况。 This article 特别让我想到重新组织我的代码,因为我看到作者在模型中实现了“类似搜索”的功能。作者的函数虽然没有处理用户输入,所以我想知道应该如何处理它。

这就是我在学习“SCFM”之前编写代码的方式:

控制器中的搜索逻辑:

#searches_controller.rb  

#This is the method invoked when second page receives POST request
def search
    @neatObjects = NeatObjects.all.to_a  

    @neatObjects.delete_if { 
        |neatObject| !matches?(neatObject, params[:searchString])
    }
end

def matches?(neatObject, searchString)
    if((neatObject.description.downcase.include? searchString.downcase) ||
       (neatObject.address.downcase.include? searchString.downcase))
        return true
    end
    return false
end

此方法通过在 NeatObject 模型上调用 .all() 来获取对所有 neatObjects 的引用(步骤 1)。它使用数组函数 delete_if 对每个 neatObject 执行 match 测试,并且只保留那些通过 (第2步)。第 3 步是自动完成的,因为我们将结果存储在为视图提供服务的控制器中的实例变量中。

将逻辑放在控制器中是非常直接的,但是在考虑“SCFM”设计模式时,它似乎非常不合逻辑。

我编写了另一个选项,其中控制器将用户的输入发送到模型中的函数,该函数将返回 匹配neatObjects 输入。

模型中的搜索逻辑

#NeatObject.rb  

def self.get_matches_for(searchString)
    all.to_a.delete_if { |neighborhood| !matches?(searchString, neighborhood) }
end

def self.matches?(phrase, neighborhood)
    fields = [neighborhood.name, neighborhood.address]

    fields.map!(&:downcase)
    phrase.downcase!

    fields.each do |field|
        if (
            (phrase.include? field) ||
            (field.include? phrase)
           )
            return true
        end
    end

    return false
end  

此方法使用 all() 获取 neatObjects 的完整列表(第 1 步)。与第一种方法一样,model 方法使用 delete_if 删除不符合特定条件的数组元素 (neatObjects)(通过 match 测试)(步骤 2)。在这个方法中,服务于视图的控制器会在 NeatObject 模型上调用 get_matches_for,并将结果存储在实例变量中(步骤 3),如下所示:@neatObjects = NeatObject.get_matches_for( params[:searchString] )

我确实认为模型选项更简洁,并且更易于维护,但我将在下一节中更深入地介绍。


担忧

我可以看到模型方法和控制器方法的优缺点,但有些事情我仍然不确定。

当我阅读我多次引用的那篇文章时(就像我读过here),模型定义了一个函数来返回最近添加的人是非常合乎逻辑的。

控制器不必执行逻辑来确定最近是否添加了。控制器不应该这样做是有道理的,因为这取决于数据本身。 messages 的“新近度”测试可能有完全不同的实现。最近的人可能包括本周添加的人,而最近的消息只是今天发送的那些消息。

控制器应该能够说出People.find_recentMessage.find_recent 并知道它得到了正确的结果。

  • find_recent 方法也可以修改为接受时间符号并返回不同时间段的对象是否正确?例如 - People.find_in_time( :before_this_month )Messages.find_in_time( :last_year )。这是否仍然遵循 MVC 模式和 Rails 约定?

  • 控制器是否应该能够找到匹配以供NeatObject.get_matches_for( searchString ) 的用户输入?'

我认为匹配逻辑属于模型,因为在测试中使用了某些/特定的属性,这些属性因数据而异。我们可能对不同的表有不同的属性,而这些属性肯定不应该由控制器定义。我知道控制器依赖于模型,而不是相反,所以模型必须定义这些属性,即使其余的逻辑在控制器中。

  • 如果模型定义了搜索时使用的属性,为什么不应该定义整个搜索功能?

上面的文字解释了为什么我认为模型应该处理搜索逻辑并表达了我的大部分问题/疑虑,但我确实有一些意见支持另一种选择。

  • 如果模型只关心数据,如何证明将用户输入传递给它?

如果控制器不处理搜索逻辑,它仍然需要将用户的输入发送给模型。它在发送之前清理它吗? (删除前导/尾随空格并小写字符串。)它只是发送从用户那里获得的确切输入吗?

  • 在哪里进行测试以确保输入不是恶意的?

我最大的一些问题:

  • 每个案例的逻辑走向的答案是否会改变?

如果搜索/匹配过程更简单,代码的位置会改变吗?例如,如果搜索更简单:

  • 如果被测试的唯一属性是地址并且不太可能改变,我们是否只使用控制器处理搜索?

  • 1234563 .这会不会有太多的逻辑或用户输入无法放入模型中?

总结

  • 如何才能始终确保将逻辑放置在正确的位置 (MVC)?
  • 对于我的具体情况,有了这些数据和这个搜索/匹配过程,我应该如何组织代码?
  • 当您发现灰色区域或不确定逻辑属于何处时,可以遵循哪些准则?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 model-view-controller


    【解决方案1】:

    这在不同情况下会有很大差异(每个应用程序都不同),但这是我最终要做的事情(大型体育应用程序,从许多不同的地方搜索大量表格):

    • 首先在控制器(或很少查看)中使用非重复代码模式执行小型搜索
    • 当发现不止一两个动作/视图需要代码时,我将其移至模型中

    当查询复杂度高于平均 .find 或简单 .where 时,我也会将代码移动到模型中

    这样,您不会使用一次性方法向模型发送垃圾邮件,同时不要在多个控制器/动作/视图上重复相同的代码

    【讨论】:

    • 感谢您的回答!我还不会接受它,因为我确实想看看其他用户是否有他们想要分享的不同意见。在决定像这样的设计问题时,您是否有任何资源可以帮助您?
    【解决方案2】:

    正如许多人在 IT 和生活中所想的那样,这取决于规模和目标。

    注意事项:

    1) 对于设计:如果您发现自己在控制器中多次违反 DRY,则可能是时候将您的逻辑转移到模型中了。

    2) 性能方面:由于控制器比模型加载更多,控制器中的逻辑往往表现最差。

    同样重要的是:除非您正在做一些非常琐碎的事情,并且您的 db 有几千行,否则不要使用 db 进行文本搜索。相反,请使用 Solr、ElasticSearch、Sphinx 等搜索引擎。

    【讨论】:

      猜你喜欢
      • 2012-06-02
      • 1970-01-01
      • 2023-04-07
      • 2012-02-09
      • 1970-01-01
      • 2017-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多