Rails MVC - 数据库搜索逻辑应该放在模型还是控制器中

Mat*_*t C 5 model-view-controller ruby-on-rails ruby-on-rails-4

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

- 模型视图控制器

- 配置Rails惯例

- 瘦模控制器,脂肪模型

我按照我认为最重要的顺序将它们列在这里.


我的问题

在阅读了几篇文章后,特别是这篇文章和这篇文章,我开始将控制器中的一些逻辑移到我的模型中.

但是,我无法决定是否将我的搜索逻辑(稍后深入解释)从控制器移动到模型,即使在阅读了更多这样的帖子/文章后:MVC思考,模型与控制器,分离关注点还有更多未列在这里.


场景

视图

两页:

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

  • 第二页简单地渲染给定数组中的每个neatObject,让我们称之为@coolList.

调节器

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

模型

  • NeatObject模型处理来自控制器的请求,并返回neatObjects回到那些控制器.

  • NeatObject模型定义之间的关系neatObjects在我们的数据库和其他表.

数据库

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

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

少了什么东西?

控制器如何与模型一起使用以获得用户输入的匹配.

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

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

  • 控制器是否应该向模型询问所有 neatObjects,那么只保留匹配的neatObjects

  • 解决方案是否兼而有之?

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


搜索逻辑深入

这一过程涉及发现neatObjects匹配搜索字符串.如果不定义我们认为 neatObjects 匹配内容,就不可能继续前进.为了使事情变得简单,我们会说:

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

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

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

以下是步骤概述:

  1. 获取对所有 neatObjects的引用
  2. 循环遍历每个neatObject,让每个人通过匹配测试
    1. 测试过程 - 在结果列表中添加/保留neatObject
    2. 测试失败 - 不要为结果保留neatObject
  3. 使结果可用于视图

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


履行

搜索功能可以在NeatObject模型或服务于视图的控制器中轻松实现.

通常,我只会在控制器中编写所有逻辑,但在我了解了"Skinny控制器,Fat模型"设计之后,我认为它肯定适用于这种情况.在我看到作者在模型中实现了"类似搜索"的功能之后,本文特别让我考虑重新组织我的代码.作者的功能虽然没有处理用户输入,所以我想知道应该如何处理它.

这是我在学习"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
Run Code Online (Sandbox Code Playgroud)

此方法获得其参考的所有的neatObjects通过调用(步骤1) .所有()NeatObject模型.它使用数组函数delete_if对每个neatObject执行匹配测试,并且仅保留那些通过的(步骤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  
Run Code Online (Sandbox Code Playgroud)

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

我确实认为模型选项更干净,而且更加可维护,但我将在下一节中更深入地介绍.


关注

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

当我阅读那篇文章时,我已多次引用(就像我在这里所做的那样),模型定义了一个返回最近添加的人的函数是非常合乎逻辑的.

控制器不必实施逻辑来确定最近是否添加了一个.有意义的是控制器不应该,因为这取决于数据本身.消息的"新近度"测试可能有完全不同的实现.最近的人可能包括本周添加的人,而最近的消息只是今天发送的消息.

控制器应该只能说People.find_recent或者Message.find_recent知道它得到了正确的结果.

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

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

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

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

上面的文字解释了为什么我认为模型应该处理搜索逻辑并表达我的大多数问题/关注点,但是我确实有一些观点赞成另一个选项.

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

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

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

我最大的一些问题:

  • 对于每种情况,逻辑发生变化的答案是什么?

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

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

  • 如果我们正在制作高级搜索功能,用户决定在搜索中包含哪些属性并控制其他一些因素,那么将会有很多用户输入来定义搜索功能的参数.是否会将太多的逻辑或用户输入放在模型中?


结论

  • 我怎样才能始终确保将逻辑放在正确的位置(MVC)?
  • 对于我的具体情况,使用此数据和此搜索/匹配过程,我应该如何组织代码?
  • 当您找到灰色区域或不确定逻辑属于何处时,可以遵循什么准则?

Fra*_*aña 2

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

注意事项:

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

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

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