AntiPatterns基础知识:Rails控制器
如果您一直遵循“胖模特,瘦瘦的控制者”的饮食习惯,那么您肯定已经朝着正确的方向前进。 但是,保持控制器的外观并不像听起来那么容易。 在本文中,您将学习到两种适合初学者使用的方法,这些方法可以减轻控制器的负担。
主题
- 胖控制器
- 非RESTful控制器
- 鼠巢资源
FAT控制器
好吧,“胖模特,瘦瘦的控制器”,对吗? 如果您还没有阅读过以前的《 AntiPattern》文章,那么我应该提到,针对那些保持骨感的模型和控制器,无论如何,都是更好的指南。 所有多余的脂肪都不利于您的项目-“一切都瘦了”更有意义。 (也许我应该明确地说,我与时尚界没有任何联系,也不想重复这样的印象,即如果不适合某种假想的身体,就无法被视为美丽。)
与模型一样,您希望控制器具有单一职责。 控制器应该真的很笨,可以管理流量,而别无其他。 另外,如果可能的话,我们希望使模板尽可能地哑巴-在这方面,演示者可以派上用场。
进一步重要的是,不要过多地偏离RESTful控制器操作。 当然,不时地在其中添加其他方法是很有意义的,但是在大多数情况下,使用它们会令您有些不安。 当控制器积累了实际属于模型的业务逻辑时,或者当经验不足的开发人员不使用Rails约定时,控制器往往会发胖。
您不会是第一个尝试重新发明轮子的人,当然也不会是最后一个。 不要为此感到难过,因为我们中的大多数人可能都曾去过那里,但是作为一名工匠,您确实应该投入时间和精力来了解所使用框架的约定,优点和局限性-至少出于商业目的有人为您的专业知识付费。 当然,实验总是很好。
由于控制器负责您的应用程序流程以及收集视图所需的信息,因此他们已经负有相当重要的责任。 他们真的不需要在模型领域增加复杂性。 控制器正在与您的视图紧密配合以显示模型层提供的数据。 他们的关系比模型更紧密。 模型层可能会独立于其他模型而开发。 这样做的好处是,干净的控制器层通常会对视图的整洁度产生积极影响。
我想了解的是,胖控制器在Rails领域非常普遍-特别是在初学者和经验不足的开发人员中-只需一点点爱与关怀,就可以轻松地对其进行优化。
第一步很简单。 问问自己,如果控制器的大小增加了,那么复杂性是否来自于增加的业务逻辑。 如果是这样,请找到一种方法将其移动到模型层,在该层中,您还可以获得更好的测试复杂代码的地方。
演讲者
为了遵循以上将获取的控制器逻辑移动到模型的建议,演示者可以是一种方便的技术。 他们可以在将几个松散相关的属性组合在一起的同时模拟模型,这对于保持控制器的苗条和性感很有用。 最重要的是,它们还擅长将逻辑上的麻烦拒之门外。 精心制作一个额外的物品很划算!
演示者可以“模仿”一个模型,该模型表示视图所需的状态,并组合需要在控制器中移动的属性。 它们可能更复杂,但是后来我觉得它们正在进入“装饰器”领域。
有时,一个控制器负责同时创建多个模型,我们希望避免在其中处理多个实例变量。 为什么这很重要? 因为它有助于我们保持应用程序的可维护性。 演示者汇总了行为和属性,这使我们的控制器可以轻松地将重点放在单个对象上的小型,简单的作业上。 同样,在视图中格式化数据或其他类似的小功能也是经常发生的工作。 将其包含在演示者中不仅对干净视图很有用,而且对于使这个行为的测试变得简单直接的专用位置来说,模型层也更易于测试。 更多的“赚钱”和爵士乐。
如果您偶然发现了Presenter Pattern,并找到了多种描述方法或不同方式来描述它,那么您就不会发疯。 关于主持人是什么似乎没有明确的协议。 但是,常识是它位于MVC层之间。 我们可以使用它来管理需要同时创建的多个模型对象。 在组合这些对象时,它模仿了ActiveRecord模型。
通常引用的方案是某种形式,可以输入各种不同模型的信息,例如新用户帐户也具有信用卡和地址或其他内容的输入字段。 通过依次完成几个表单来进行完整向导并没有什么不同。 由于您的应用程序的这些部分往往非常重要,因此最好保持整洁,同时拥有可用于测试的最佳选择。
在这一点上的用户体验也是关键。 在下面的示例中,我们要创建一个具有has_one
agent
和一个quartermaster
的简单任务。 没有火箭科学,但是这是一个很好的例子,可以看到事情很快就会失控。 控制器需要以嵌套形式处理视图所需的多个对象,以将事物绑定在一起。 您很快就会看到,所有这些都可以通过一个不错的“表单对象”来解决,该表单对象提供了所需的对象并将它们编织在一起在一个中心类中。
app / models / mission.rb
class Mission < ActiveRecord::Basehas_one :agenthas_one :quartermasteraccepts_nested_attributes_for :agent, :quartermaster, allow_destroy: truevalidates :mission_name, presence: true...endclass Agent < ActiveRecord::Basebelongs_to :missionvalidates :name, presence: true...endclass Quartermaster < ActiveRecord::Basebelongs_to :missionvalidates :name, presence: true...end
我在这里提到这些模型只是出于完整性的考虑,以防您以前从未使用过fields_for
—有点简化,但可以使用。 以下是问题的核心。
实例变量太多
app / controllers / missions_controller.rb
class MissionsController < ApplicationControllerdef new@mission = Mission.new@agent = Agent.new@quartermaster = Quartermaster.newenddef create@mission = Mission.new(mission_params)@agent = Agent.new(agent_params)@quartermaster = Quartermaster.new(quartermaster_params)@mission.agent = @agent@mission.quartermaster = @quartermasterif @account.save and @agent.save and @quartermaster.saveflash[:notice] = 'Mission accepted'redirect_to missions_pathelseflash[:alert] = 'Mission not accepted'render :new endendprivatedef mission_paramsparams.require(:mission).permit(:mission_name, :objective, :enemy)enddef agent_paramsparams.require(:agent).permit(:name, :number, :licence_to_kill)enddef quartermaster_paramsparams.require(:quartermaster).permit(:name, :number, :hacker, :expertise, :humor)endend
总体而言,很容易看出这是朝错误的方向前进。 它已经吸引了相当多的群众,并且只包含new
方法和create
方法。 不好! 私有方法的堆积速度也太快了。 有agent_params
和quartermaster_params
在MissionsController
听起来不是很光滑给你,我的希望。 您认为难得一见吗? 恐怕不是。 控制器中的“单一职责”确实是一个黄金准则。 一分钟后,您就会明白为什么。
即使您起眼睛,这看起来也很讨厌。 在保存create
动作并进行验证的过程中,如果由于某些错误或某些原因我们无法保存每个对象,那么我们将得到孤立的对象,没人愿意处理。 kes!
当然,我们可以将其放入transaction
块,仅当所有对象都按顺序排列时,该块才能成功完成保存,但这有点像在当前环境中冲浪—而且,为什么控制器中确实需要像这样的模型级东西? 有更多优雅的方式可以吸引潮流。
按照此路径,该视图将具有用于@mission
的随附form_for
和用于@agent
和@quartermaster
的附加fields_for
。
多个对象的凌乱形式
app / views / missions / new.html.erb
<%= form_for(@mission) do |mission| %><h3>Mission</h3><%= mission.label :mission_name %><%= mission.text_field :mission_name %><%= mission.label :objective %><%= mission.text_field :objective %><%= mission.label :enemy %><%= mission.text_field :enemy %><h3>Agent</h3><%= fields_for @agent do |agent| %><%= agent.label :name %><%= agent.text_field :name %><%= agent.label :number %><%= agent.text_field :number %><%= agent.label :licence_to_kill %><%= agent.check_box :licence_to_kill %><% end %><h3>Quartermaster</h3><%= fields_for @quartermaster do |quartermaster| %><%= quartermaster.label :name %><%= quartermaster.text_field :name %><%= quartermaster.label :number %><%= quartermaster.text_field :number %><%= quartermaster.label :hacker %><%= quartermaster.check_box :hacker %><%= quartermaster.label :expertise %><%= quartermaster.text_field :expertise %><%= quartermaster.label :humor %><%= quartermaster.check_box :humor %><% end %><%= mission.submit %>
<% end %>
当然可以,但我不会为此感到兴奋。 fields_for
当然很方便,但是用OOP处理这个要fields_for
。 在这种情况下,演示者还可以帮助我们简化视图,因为表单仅处理单个对象。 这样就不必嵌套表格了。 顺便说一句,我省略了用于设计表单样式的所有包装程序,以使其在视觉上更容易消化。
表单对象演示者
app / views / missions / new.html.erb
<%= form_for @mission_presenter, url: missions_path do |mission| %><h3>Mission</h3><%= mission.label :mission_name %><%= mission.text_field :mission_name %><%= mission.label :objective %><%= mission.text_field :objective %><%= mission.label :enemy %><%= mission.text_field :enemy %><h3>Agent</h3><%= mission.label :agent_name %><%= mission.text_field :agent_name %><%= mission.label :agent_number %><%= mission.text_field :agent_number %><%= mission.label :licence_to_kill %><%= mission.check_box :licence_to_kill %><h3>Quartermaster</h3><%= mission.label :quartermaster_name %><%= mission.text_field :quartermaster_name %><%= mission.label :quartermaster_number %><%= mission.text_field :quartermaster_number %><%= mission.label :hacker %><%= mission.check_box :hacker %><%= mission.label :expertise %><%= mission.text_field :expertise %><%= mission.label :humor %><%= mission.check_box :humor %><%= mission.submit %>
<% end %>
您可以轻松地看到,我们的视图变得更加简单-没有嵌套,并且此套间更加简单。 您需要注意的部分是:
<%= form_for @mission_presenter, url: missions_path do |mission| %>
您需要通过url
为form_for
提供路径,以便它可以将参数从此表单“发布”到其适当的控制器(此处为MissionsController
。 如果没有额外的参数时,Rails会尝试寻找我们的主持人对象控制器@mission_presenter
通过约定,在这种情况下MissionFormPresentersController
-and炸毁没有之一。
总的来说,我们应该尽最大努力使控制器动作尽可能简单,就像处理CRUD对资源的操作一样—这就是控制器的工作目的,并且在不混淆MVC区别的情况下最有能力做到。 作为一个很好的副作用,控制器中的复杂性级别也会降低。
app / controllers / missions_controller.rb
class MissionsController < ApplicationControllerdef new@mission_presenter = MissionFormPresenter.newenddef create@mission_presenter = MissionFormPresenter.new(mission_params)if@mission_presenter.saveflash[:notice] = 'Mission accepted'redirect_to missions_pathelseflash[:alert] = 'Mission not accepted'render :newendendprivatedef mission_paramsparams.require(:mission_form_presenter).permit(whitelisted)enddef whitelisted[:mission_name, :objective, :enemy, :agent_name, :agent_number, :licence_to_kill, :quartermaster_name, :quartermaster_number, :hacker, :expertise, :humor]end
end
控制器在眼睛上也容易很多,不是吗? 更清洁,更标准的控制器动作。 我们正在处理具有一项工作的单个对象。 我们实例化一个对象(演示者),并像往常一样为它提供参数。
困扰我的唯一一件事就是发送了这么长的白名单强参数列表。 我将它们提取到一个名为whitelisted
的方法中,该方法仅返回带有完整参数列表的数组。 否则, mission_params
可能看起来像下面的样子-感觉太讨厌了:
def mission_paramsparams.require(:mission_form_presenter).permit(:mission_name,:objective, :enemy,:agent_name, :agent_number, :licence_to_kill, :quartermaster_name, :quartermaster_number, :hacker, :expertise, :humor)
end
哦,关于:mission_form_presenter
的:mission_form_presenter
参数的params.require
。 尽管我们为演示者@mission_presenter
命名了实例变量,但当我们将其与form_for
一起使用时,Rails希望表单的params哈希键以实例化的对象命名,而不是以控制器中给定的名称命名。 我已经看过几次新手之旅。 在这种情况下,Rails为您提供了隐秘的错误,也无济于事。 如果您需要对参数进行一些复习,这是一个深入了解的好地方:
- 文献资料
- 免费截屏
在我们的Mission
模型中,我们现在不再需要accepts_nested_attributes
,并且可以摆脱看上去无害的可怕事物。 validates
方法在这里也无关紧要,因为我们将此责任添加到了表单对象中。 当然,对Agent
和Quartermaster
验证也是如此。
app / models / mission.rb
class Mission < ActiveRecord::Basehas_one :agenthas_one :quartermaster#accepts_nested_attributes_for :agent, :quartermaster, allow_destroy: true#validates :mission_name, presence: true...end
将验证逻辑直接封装在我们的新对象上有助于我们保持事物的整洁有序。 当然,如果您还可以彼此独立地创建这些对象,则验证应该保留在它们当前所在的位置。 这种重复也可以解决,例如,通过validates_with
带有从ActiveModel::Validator
继承的单独的验证类使用validates_with
。
现在,我们有了一个瘦身的控制器,只需一个职责,并且可以使用扁平形式并行创建多个对象。 太棒了! 我们如何实现所有这一切? 下面是演示者完成的所有工作,尽管这并不意味着该课程可以完成很多工作。 我们想要一种没有数据库的中间模型,该数据库可以处理多个对象。 看看这个普通的旧Ruby对象(PORO)。
app / presenters / mission_form_presenter.rb
class MissionFormPresenterinclude ActiveModel::Modelattr_accessor :mission_name, :objective, :enemy, :agent_name,:agent_number, :licence_to_kill, :quartermaster_name,:quartermaster_number, :hacker, :expertise, :humorvalidates :mission_name, :agent_name, :quartermaster_name, presence: truedef saveActiveRecord::Base.transaction do@mission = Mission.create!(mission_attributes)@mission.create_agent!(agent_attributes)@mission.create_quartermaster!(quartermaster_attributes)endendprivate def mission_attributes{ mission_name: mission_name, objective: objective, enemy: enemy }enddef agent_attributes{ name: agent_name, number: agent_number, licence_to_kill: licence_to_kill }enddef quartermaster_attributes{ name: quartermaster_name, number: quartermaster_number, hacker: hacker, expertise: expertise, humor: humor }end
end
我认为可以说这不是很复杂,这很公平。 MissionFormPresenter
是一个表单对象,现在封装了使我们的控制器变得不必要的胖的原因。 另外,我们的观点变得平坦而简单。 这里发生的是,我们可以聚合表单中的所有信息,然后依次创建所需的所有对象。
最重要的部分发生在我们的新save
方法中。 首先,我们创建新的Mission
对象。 之后,我们可以创建与其关联的两个对象: Agent
和Quartermaster
。 通过has_one
和belongs_to
关联,我们可以使用适合于关联对象名称的create_x
方法。
例如,如果我们使用has_one :agent
,则会得到一个create_agent
方法。 容易吧? (实际上,我们也有一个build_agent
方法。)我决定使用带有bang(!)的版本,因为如果尝试保存时记录无效,它将引发ActiveRecord::RecordInvalid
错误。 这些爆炸方法包装在transaction
块内部,当启动某些验证时,请注意不要保存孤立的对象。如果在保存过程中出现问题,则事务块将回滚。
您可能会问,这些如何与属性一起使用? 我们通过include ActiveModel::Model
( API )向Rails寻求一点爱。 这使我们可以使用属性哈希来初始化对象,这正是我们在控制器中所做的。 之后,我们可以使用attr_accessor
方法提取属性以实例化我们真正需要的对象。
ActiveModel::Model
进一步使我们能够与视图和控制器进行交互。 除其他优点外,您还可以在此类中使用它进行验证。 将这些验证放入这样的专用表单对象中对于组织而言是个好主意,而且还可以使您的模型更加整洁。
我决定将一长串参数提取到专用方法中,以提供在save
创建的对象。 在这样的演示者对象中,我几乎不必担心周围有几个私有方法。 为什么不? 感觉更清洁!
在将多个模型组合在一起的情况下测试此类情况应格外小心—所涉及的对象越简单,测试体验就越好。 确实没有火箭科学。 主持人对此表示欢迎。 将这些测试潜在地绑定到控制器上并不是解决此问题的最佳方法。 请记住,单元测试既快速又便宜。
请注意。 不要过度使用演示者-他们不应该是您的首选。 通常,对演示者的需求随着时间的流逝而增长。 就我个人而言,当您需要将多个模型表示的数据整合到一个视图中时,最好使用它们。
没有演示者,您可能经常会在控制器中为单个视图准备多个实例变量。 仅此一项就可以使他们真正肥胖,真正快速。 您应该考虑并考虑的一件事是,当演示者将对象添加到您的代码库中时,他们还可以减少控制器需要处理的对象数量,从而减少复杂性和单一职责。 减少一些脂肪可能是一种相当先进的技术,但是当您想减肥时,您需要进行一些工作。
非RESTful控制器
不尝试遵循标准的控制器动作很可能是个坏主意。 拥有大量的自定义控制器方法是一个AntiPattern,您可以轻松避免。 诸如login_user
, activate_admin
, show_books
类的方法以及代表new
, create
, show
等的其他有趣的业务,应该为您提供一个暂停和怀疑您的方法的理由。 不遵循RESTful方法很容易导致使用大量控制器,这很可能是因为您需要不时争夺框架或重新发明轮子。
简而言之,不是一个好主意。 而且,通常是缺乏经验或粗心的症状。 在这种情况下,遵循“单一责任原则”似乎也很困难,不过这只是有根据的猜测。
以RESTful方式访问控制器中的资源将使您的生活变得不那么复杂,并且您的应用程序也更易于维护,从而增加了应用程序的整体稳定性。 从对象生命周期的角度考虑以REST方式处理资源。 您创建,更新,显示(单个或集合),更新和销毁它们。
在大多数情况下,这可以完成工作。 仅供参考, new
动作和edit
动作实际上并不是REST的一部分-它们更像show
动作的不同版本,可帮助您呈现资源生命周期中的不同阶段。 在大多数情况下,这七个标准控制器动作在一起,可以为您提供在控制器中管理资源所需的全部功能。 另一个重要的优点是,其他使用您的代码的Rails开发人员将能够更快地导航您的控制器。
遵循RESTful酷帮助的这一行,这还包括命名控制器的方式。 您正在处理的资源的名称应在控制器对象中进行镜像。
例如,拥有一个可以处理@mission
对象以外的其他资源的MissionsController
,这@mission
。 控制器的绝对大小通常也是忽略REST的致命一击。 如果您遇到实现大量违反约定的自定义方法的大型控制器,将其拆分为多个具有重点职责的独特控制器是一个非常有效的策略-在遵循RESTful风格的同时基本上仅管理单个资源。 积极地将它们分开,您将可以更轻松地用Rails的方式编写它们的方法。
鼠巢资源
看下面的例子,问问自己这是怎么回事:
嵌套AgentsController
app / controllers / agents_controller.rb
class AgentsController < ApplicationControllerdef indexif params[:mission_id]@mission = Mission.find(params[:mission_id])@agents = @mission.agentselse@agents = Agent.allendend
end
在这里,我们检查是否有嵌套的路由为我们提供了可能的@mission
对象的ID。 如果是这样,我们希望使用关联的对象从中获取agents
。 否则,我们将获取该视图的所有代理的列表。 看起来无害,尤其是因为它仍然很简洁,但这是可能更大的老鼠窝的开始。
嵌套路线
resources :agents
resources :missions doresources :agents
end
对于这里的嵌套路由,没有什么不好的。 通常,这种方法没有错。 我们应该注意的事情是控制器如何处理这项业务,因此,视图需要如何适应它。 如下所示,并不是完全干净利落。
有不必要条件的视图
app / views / agents / index.html.erb
<% if @mission %><h2>Mission</h2><div><%= @mission.name %></div><div><%= @mission.objective %></div><div><%= @mission.enemy %></div>
<% end %><h2>Agents</h2>
<ul><% @agents.each do |agent| %><li class='agent'><div>Name: <%= agent.name %></div><div>Number: <%= agent.number %></div><div>Licence to kill: <%= agent.licence_to_kill %></div><div>Status: <%= agent.status %></div></li><% end %>
</ul>
可能看起来也没什么大不了的,我明白了。 但是,复杂性级别并不完全是真实世界。 除此之外,争论的重点还在于以面向对象的方式处理资源以及充分利用Rails的优势。
我想这是关于单一职责的边缘案例。 即使我们还有第二个对象( @mission
该关联仍然存在,但这并没有完全违反该想法。 但是,由于我们使用它来访问一组特定的代理,所以这完全可以。
分支是微不足道的部分,很可能导致错误的设计决策(包括视图和控制器)。 使用相同方法创建两个版本的@agents
是这里的肇事者。 我会简短地说,这确实可以很快解决。 一旦您开始嵌套这样的资源,新老鼠很快就会流连忘返。
并且上面的视图还需要一个条件,该条件适合于当您将@agents
与@agents
关联时的@mission
。 如您所见,控制器中的一些草率行为可能导致膨胀的视图具有比所需更多的代码。 让我们尝试另一种方法。 灭虫时间!
独立控制器
而不是嵌套这些资源,我们应该为该资源的每个版本赋予其自己独特的,集中的控制器—一个用于“简单”,未嵌套代理的控制器,一个用于与任务相关联的代理的控制器。 我们可以通过在/missions
文件夹下命名其中之一来实现此目的。
app / controllers / missions / agents_controller.rb
module Missionsclass AgentsController < ApplicationControllerdef index@mission = Mission.find(params[:mission_id])@agents = @mission.agentsendend
end
通过将此控制器包装在模块中,我们可以避免让AgentsController
从ApplicationController
继承两次。 没有它,我们将遇到这样的错误: Unable to autoload constant Missions::AgentsController
。 我认为模块是让Rails自动加载愉快的代价。 第二个AgentsController
可以和以前一样AgentsController
在同一文件中。 现在,它仅处理index
一种可能资源-为所有特工做好准备,而无需执行任务。
app / controllers / agents_controller.rb
class AgentsController < ApplicationControllerdef index@agents = Agent.allend
end
当然,如果代理与任务相关联,我们还需要指示路线寻找这个新的命名空间控制器。
resources :agents
resources :missions doresources :agents, controller: 'missions/agents'
end
在指定嵌套资源具有命名空间的控制器之后,我们都准备就绪。 在终端中进行rake routes
检查时,我们会看到新的控制器已命名空间,并且一切顺利。
新路线
Prefix Verb URI Pattern Controller#Actionroot GET / agents#indexagents GET /agents(.:format) agents#indexPOST /agents(.:format) agents#createnew_agent GET /agents/new(.:format) agents#newedit_agent GET /agents/:id/edit(.:format) agents#editagent GET /agents/:id(.:format) agents#showPATCH /agents/:id(.:format) agents#updatePUT /agents/:id(.:format) agents#updateDELETE /agents/:id(.:format) agents#destroymission_agents GET /missions/:mission_id/agents(.:format) missions/agents#indexPOST /missions/:mission_id/agents(.:format) missions/agents#createnew_mission_agent GET /missions/:mission_id/agents/new(.:format) missions/agents#new
edit_mission_agent GET /missions/:mission_id/agents/:id/edit(.:format) missions/agents#editmission_agent GET /missions/:mission_id/agents/:id(.:format) missions/agents#showPATCH /missions/:mission_id/agents/:id(.:format) missions/agents#updatePUT /missions/:mission_id/agents/:id(.:format) missions/agents#updateDELETE /missions/:mission_id/agents/:id(.:format) missions/agents#destroy
现在,我们用于agents
嵌套资源已正确重定向到controllers/missions/agents_controller.rb
并且每个操作都可以处理作为任务一部分的座席。 为了完整起见,我们也来看看我们的最终观点:
有使命的特工
app / views / missions / agents / index.html.erb
<h2>Mission</h2>
<div><%= @mission.mission_name %></div>
<div><%= @mission.objective %></div>
<div><%= @mission.enemy %></div><h2>Agents</h2>
<ul><% @agents.each do |agent| %><li class='agent'><div>Name: <%= agent.name %></div><div>Number: <%= agent.number %></div><div>Licence to kill: <%= agent.licence_to_kill %></div></li><% end %>
</ul>
没有任务的特工
app / views / agents / index.html
<h2>Agents</h2>
<ul><% @agents.each do |agent| %><li class='agent'><div>Name: <%= agent.name %></div><div>Number: <%= agent.number %></div><div>Licence to kill: <%= agent.licence_to_kill %></div></li><% end %>
</ul>
好吧,让我们摆脱重复的一点,我们也在@agents
进行迭代。 我创建了一个用于呈现代理列表的partial,并将其放入views
下的新shared
目录中。
app / views / shared / _agents.html.erb
<h2>Agents</h2>
<ul><% @agents.each do |agent| %><li class='agent'><div>Name: <%= agent.name %></div><div>Number: <%= agent.number %></div><div>Licence to kill: <%= agent.licence_to_kill %></div></li><% end %>
</ul>
这里没有新内容或令人惊讶的地方,但我们的观点现在更加干燥。
有使命的特工
app / views / missions / agents / index.html.erb
<h2>Mission</h2>
<div><%= @mission.mission_name %></div>
<div><%= @mission.objective %></div>
<div><%= @mission.enemy %></div><%= render "shared/agents", collection: @agents %>
没有任务的特工
app / views / agents / index.html
<%= render "shared/agents", collection: @agents %>
pe!
最后的想法
我认为,如果您初学者可以在控制器中避免使用这些AntiPatterns,那么您将有一个很好的开始。 在这方面,您还有很多需要学习的地方,但是要花点时间,这绝非易事或一夜之间。 另一方面,如果您渴望更多并且喜欢探索更高级的技术,那么我当然会全力以赴。 不要让自己对“高级”名称标签感到沮丧。
花点时间,玩得开心,如果您需要重新讨论该主题,也不要灰心,因为您还没有解决所有难题。 如果您是开发游戏的早期者,并且已经开始尝试设计模式,那么我相信您已经领先于游戏,并且已经做出了正确的决定。
不要等待,走出舒适区,让您的灰质稍稍舒展。
翻译自: https://code.tutsplus.com/articles/antipatterns-basics-rails-controllers--cms-25900
AntiPatterns基础知识:Rails控制器相关推荐
- SDN控制器测试专题一:基础知识篇
前言 SDN落地,测试先行.足以说明测试在SDN技术发展中起着举足轻重的作用.那么如何测试SDN,测好SDN,这就要求我们对SDN有一个很深的认识,对SDN的功能有一个全面的了解.本文将从SDN发展背 ...
- 计算器 控制器 计算机的神经中枢,计算机基础知识(9).ppt
每个扇区大小为:512B(字节) 1.44M软盘有:有2面 每面有:80个磁道 每个磁道有:18个扇区 容量=2*80*18*512=1474560B=1440KB 约=1.44MB (一).计算机硬 ...
- 【STM32F429开发板用户手册】第40章 STM32F429的LCD控制器LTDC基础知识和HAL库API
最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255 第40章 STM32F429的LCD控制器LTDC基础 ...
- 力士乐plc培训资料_Rexroth力士乐可编程控制器(PLC)的基础知识
德国Rexroth力士乐可编程控制器(PLC)是一种专门为在工业环境下应用而设计的数字运算操作的电子装置.它采用可以编制程序的存储器,用来在其内部存储执行逻辑运算.顺序运算.计时.计数和算术运算等操作 ...
- Python基础笔记_Day01_计算机基础知识和Python开发环境搭建
Day01_计算机基础知识和Python开发环境搭建 目录 01.01_计算机基础知识(计算机概述)(了解) 01.02_计算机基础知识(软件开发和计算机语言概述)(了解) 01.03_计算机基础知识 ...
- 嵌入式Linux的OTA更新,基础知识和实现
嵌入式Linux的OTA更新,基础知识和实现 OTA updates for Embedded Linux, Fundamentals and implementation 更新的需要 一旦嵌入式Li ...
- 《计算机网络应用基础》模拟试卷(六),《计算机与网络应用基础知识1》模拟试卷...
<计算机与网络应用基础知识1>模拟试卷 (4页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 <计算机与网络应用基础知识1& ...
- 华南理工计算机基础知识题,华南理工_计算机应用基础_随堂练习答案(2017年)
华南理工_计算机应用基础_随堂练习答案(2017年) (18页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.9 积分 . . . .华南理工-计算 ...
- 华南理工网络计算机基础知识,2019年华南理工大学网络教育计算机基础随堂练习第一章...
2019年华南理工大学网络教育计算机基础随堂练习第一章 (9页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 11.90 积分 第一章 计算机基础知识·第 ...
最新文章
- fpga中的case语句
- 数据结构第一次作业——抽象数据类型
- ACL 2021 | SimCLS: 概念简单但足够有效的对比学习摘要生成框架
- 糖豆人维修服务器多长时间,服务器不稳定的《糖豆人》凭啥还这么火?只因做到了这三点...
- 国科大高级人工智能10-强化学习(多臂赌博机、贝尔曼)
- mysql memory inno_如何定位RDS MySQL内存使用率高?-阿里云开发者社区
- 计算机科学与技术专业导论_“课程思政”建设经验分享 | 王振武:专业导论(计算机科学与技术)...
- 《linux内核完全注释》读书笔记 2
- 数据结构--------单链表+面试题
- mysql安装显示挂起_安装SQL时提示有挂起的文件操作无法安装的解决
- BOOST升压电路原理详解
- 计算机网络技术练习,计算机网络技术基础各章节综合练习题及答案
- fastadmin表格操作
- 在Windows Server 2019上部署Deskpool桌面云系统
- RFX2401C skyworks射频2.4GHZ ZIGBEE/ISM发射/接收RFeIC
- 中国音视频SaaS第一股,百家云(RTC)正式登陆纳斯达克,估值达8.4亿美元
- 无线洗地机哪款性价比高?高性价比的洗地机分享
- outlook你的邮件服务器证书无效,安卓手机outlook无法登录、添加帐户
- Google退出中国了
- LeetCode刷题(45)~位1的个数【布赖恩·克尼根算法】
热门文章
- 商品加入购物车的动画
- 鸿蒙系统发布时间,太激动了!鸿蒙系统首批升级机型名单公布,看看有你手机吗?...
- Clouder—构建企业级数据分析平台-墨羽@袋鼠云
- 互联网人逃离相互宝:不是用户“保命符“,却是阿里“试金石“
- 03-第一个脚本程序以及输入输出_Python编程之路
- es修改network.host
- 计算机二级C语言233网校,二级计算机考点
- 网络攻防基础(复习)
- 将网页打包成桌面应用,内置使用ie浏览器打开
- android 分享微信小程序失败,从一次失败的微信小程序抓包、反编译经历中学习反思...