作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
布莱恩VanLoo的头像

布莱恩VanLoo

Brian是一名自由软件开发人员,他在企业界提供技术价值. 具有从嵌入式系统到大规模web应用程序的开源技术的专业知识.

以前在

Stitch Fix
Share

编者注:本文由我们的编辑团队于2022年11月30日更新. 它已被修改,以包括最近的来源,并与我们目前的编辑标准保持一致.

Ruby on Rails (Rails)是一个流行的开源框架,基于 Ruby 一种致力于简化web应用程序开发过程的编程语言.

Rails是基于以下原则构建的 约定优于配置 并承诺一个有利的结果 专家开发人员 谁遵循最佳实践惯例.

虽然这种模式有它的优点, 它并非没有缺陷,并且为开发人员犯常见的Rails编程错误敞开了大门. 框架背后发生的神奇之处有时会让开发人员感到困惑. 它还可能在安全性和性能方面产生不良后果.

因此,Rails易于使用,但同样容易被误用. 本教程着眼于10个常见的Ruby on Rails编码问题,并向您展示如何避免它们以及它们引起的问题.

常见Rails编程错误#1:用逻辑重载控制器

Rails是基于 MVC体系结构. 在Rails社区中,我们一直在讨论 胖模特,瘦控制器 有一段时间了,但是我最近继承的几个Rails应用程序违反了这一原则. 移动视图逻辑(最好放在helper中)太容易了。, 或者域/模型逻辑, 输入控制器.

这样做的问题是控制器对象将开始违反 单一责任原则,使得将来对代码库的更改变得困难且容易出错.

一般来说,控制器中应该包含的逻辑类型有:

  • 会话和cookie处理. 这可能包括 认证/授权 或者其他cookie处理.
  • 选择模型. 给定从请求传入的参数,用于查找正确模型对象的逻辑. Ideally, 模型选择应该是对单个find方法的调用,该方法设置一个实例变量,以便稍后用于呈现响应.
  • 请求参数管理. 收集请求参数并调用适当的模型方法来持久化它们.
  • 呈现/重定向. 呈现结果(HTML、XML、JSON等).)或重新定向,视情况而定.

这就突破了单一职责原则的极限, 它是Rails框架要求我们在控制器中拥有的最小值.

常见Rails编程错误#2:用逻辑重载视图

开箱即用的Rails模板引擎, ERB,是构建内容可变的页面的好方法. However, 如果你不小心, 你很快就会得到一个由HTML和Ruby代码混合而成的大文件,很难管理和维护. 这也会导致重复和违反 不要重复自己 (干)原则.

这种HTML和Ruby的笨拙混合可以以多种方式表现出来, 其中之一是视图中过度使用条件逻辑. 作为一个简单的例子,考虑这样一个情况 current_user 方法,该方法返回当前登录的用户. Often, 在这种情况下, 在视图文件中,我们会得到这样的条件逻辑结构:

Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %>

代替在HTML中嵌入条件逻辑, 最好确保返回的对象是 current_user 总是设置—无论是否有人登录(i.e.,如果没有登录,则为null),并且视图方法可以处理null用户引用. 例如,您可以定义 current_user helper in app / controllers / application_controller like this:

需要“ostruct”

helper_method: current_user

def current_user
    @current_user ||= User.如果会话[:user_id],查找会话[:user_id]
    如果@current_user
        @current_user
    else
OpenStruct.新(名字:“客人”)
    end
end

这将使你能够用这一行简单的代码替换之前的视图代码示例:

Welcome, <%= current_user.name -%>

下面是另外两个推荐的Ruby on Rails编码最佳实践:

  • Use 查看布局和局部 适当地封装页面上的重复项目.
  • 使用演示器/装饰器,比如 Draper gem 将视图构建逻辑封装在Ruby对象中. 然后,可以向该对象添加执行逻辑操作的方法,而不是将这些方法放入视图代码中.

常见的Rails编程错误#3:用逻辑重载模型

给出最小化视图和控制器中的逻辑的指导, MVC架构中唯一能放置所有逻辑的地方就是模型了, right?

不完全是.

许多Rails开发人员实际上犯了这个错误,并将所有内容存储在他们的 ActiveRecord 建模类,这导致 Mongo 不仅违反单一责任原则的文件, 但同时也是维护的噩梦.

功能——比如生成电子邮件通知, 与外部服务的接口, 转换为其他数据格式, 这和一个人的核心责任没有太大关系 ActiveRecord 模型,它应该只是在数据库中查找和持久化数据.

如果逻辑不应该出现在视图中, 它不应该进入控制器, 它不应该进入模型, 它应该放在哪里??

Enter 普通的旧式Ruby对象 (POROs). 使用像Rails这样的综合框架, 新开发人员可能不愿意在框架之外创建自己的类. However, 将逻辑从模型移到poro中通常是避免模型过于复杂的方法. With POROs, 你可以将邮件通知或API交互之类的东西封装到它们自己的类中, 而不是把他们安置在 ActiveRecord model.

因此,考虑到这一点,一般来说,你的模型中唯一应该保留的逻辑是:

  • ActiveRecord 配置 i.e.,关系和验证.
  • 简单突变法 封装更新和保存数据库中的少量属性.
  • 访问包装 隐藏内部模型信息(e.g., a full_name 方法 first_name and last_name 数据库中的字段).
  • 复杂的查询 那是比简单更复杂的 find; generally speaking, you should never use the where 方法,或任何其他类似的查询构建方法,在模型类之外.

常见的Rails编程错误#4:使用泛型助手类作为垃圾场

这个错误是前一个错误3的必然结果. Rails框架将重点放在MVC的命名组件上.e.(模型、视图和控制器)框架. 虽然存在定义来描述我们可以在每个组件的类中放置什么, 有时候,我们开发的方法似乎不适合这三种方法中的任何一种.

Rails生成器方便地为我们创建的每个新资源构建一个助手目录和一个新的助手类. 将任何不适合模型的功能塞进模型是非常诱人的, view, 或者将控制器放入helper类中.

而Rails肯定是以mvc为中心的, 没有什么可以阻止您创建自己的类类型,并添加适当的目录来包含这些类的代码. 当您开发新功能时, 考虑哪些方法可以很好地组合在一起,并为包含这些方法的类分配描述性名称. 使用像Rails这样全面的框架并不是放弃优秀的面向对象设计最佳实践的借口.

常见的Rails编程错误#5:太多宝石

同时支持Ruby和Rails 丰富的宝石生态系统 它们共同提供了开发人员能够想到的任何功能,非常适合快速构建复杂的应用程序. 然而,我也见过许多臃肿的应用程序 Gemfile 与它提供的功能相比不成比例地大.

过度使用gem会使Rails进程的规模超出所需, 降低生产中的性能. 除了用户的挫败感, 这还可能导致需要更大的服务器内存配置并增加操作成本.

过多的gem可能会导致更大的Rails应用程序需要更长的时间来启动, 这会使开发变慢,并使自动化测试花费更长的时间(并且作为规则, 慢的测试不会经常运行).

请记住,您带入应用程序的每个宝石可能, in turn, 是否依赖于其他gem, 它们也可能有自己的依赖关系. 因此,添加宝石可以产生复合效果. 例如,添加 rails_admin gem将带来总共11个以上的宝石-比基础Rails安装增加10%.

在撰写本文时,一个新的Rails 7.0.4 .安装中包含了几十个宝石 Gemfile.lock file. 这比它包含的要多 Gemfile 它代表了少量标准Rails gems作为依赖项引入的所有gems.

当你添加每颗宝石时,仔细考虑额外的开销是否值得. 例如,开发人员通常会添加 rails_admin Gem为模型结构提供了一个很好的web前端. 但是这个珍宝只不过是一个花哨的数据库浏览工具. 即使您的应用程序需要管理员用户具有额外的权限, 您可能不想让它们访问原始数据库. 通过开发自己的、更精简的管理功能,您会得到更好的服务.

常见Rails编程错误#6:忽略日志文件

虽然大多数Rails开发人员可能知道默认日志文件, 只有很少一部分人真正注意到了这些文件中的信息. 在生产中,诸如 Honeybadger or New Relic 可以为我们处理日志监控吗. 然而,在我们开发和测试应用程序时,密切关注日志文件是至关重要的.

Rails的许多神奇之处都发生在应用程序的模型中. 通过在模型中定义关联, 这是很容易拉关系,并有一切可以让你的观点. 填充模型对象所需的所有SQL都为您生成了. 那太好了. 但是如何确认生成的SQL是有效的呢?

你经常会遇到的一个例子是 N+1查询问题. 虽然无法实时观察, N+1查询问题有很好的文档记录,并且可以通过查看记录的SQL查询来检测.

Say, 例如, 在一个典型的博客应用程序中,你有下面的查询,你将显示一组选定的帖子的所有评论:

def comments_for_top_three_posts
    posts =职位.limit(3)
    posts.Flat_map do |post|
        post.comments.to_a
    end
end

调用此方法的请求的日志文件可能会显示单个查询获得三个post对象, 和之后是三个查询,以获得每个对象的评论:

开始GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700
处理由PostsController#some_comments作为HTML
    后负荷(0).4毫秒)SELECT“posts”.* FROM“posts”LIMIT 3
    评论负载(5).6毫秒)选择“评论”.* FROM "comments" WHERE "comments".“post_id”= ?  [[“post_id”,1]]
    评论负载(0).4毫秒)SELECT“comments”.* FROM "comments" WHERE "comments".“post_id”= ?  [[“post_id”2]]
    评论负载(1).5毫秒)SELECT“comments”.* FROM "comments" WHERE "comments".“post_id”= ?  [[“post_id”,3]]
    呈现文章/ some_comments.html.布局/应用中的动词(12.5ms)
在581ms内完成200个OK(查看次数:225.8ms | ActiveRecord: 10.0ms)

ActiveRecord’s 立即加载 如果预先指定要加载的所有关联,那么Rails中的功能可以显著减少查询的数量. 这是通过调用 includes (or preload)方法。ActiveRecord::关系正在建造的物体. With includes, ActiveRecord ensures that all of the specified associations are loaded using the minimum possible number of queries; e.g.:

def comments_for_top_three_posts
    posts =职位.包括(评论).limit(3)
    posts.Flat_map do |post|
        post.comments.to_a
    end
end

如果我们在执行修改后的代码后检查日志文件, 我们将看到所有post对象的评论都被收集在一个查询中:

开始GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700
处理由PostsController#some_comments作为HTML
    后负荷(0).5毫秒)SELECT“posts”.* FROM“posts”LIMIT 3
    评论负载(4).4毫秒)SELECT“comments”.* FROM "comments" WHERE"comments "."post_id" IN (1,2,3)
    呈现文章/ some_comments.html.布局/应用中的动词(12.2ms)
560ms内完成200个OK(视图数:219.3ms | ActiveRecord: 5.0ms)

效率更高. 这种针对N+1查询问题的解决方案是一种低效率的例子,如果我们仔细观察开发人员没有注意日志文件的应用程序,我们可能会发现这种低效率.

在开发期间检查开发和测试日志文件是一种很好的方法,可以提示代码中的任何低效率,以便在应用程序投入生产之前纠正您的工作. 而且由于您在开发或测试中使用的数据集可能比生产中的数据集小得多, 在开发过程中尽早解决这个问题. 如果你正在开发一款新应用, 您的生产数据集开始时可能很小,但是, 随着它的成长, Rails会降低应用程序的速度.

考虑 清理日志文件的步骤 多余的或无关紧要的信息.

常见的Rails编程错误#7:缺乏自动化测试

默认情况下,Ruby和Rails提供了强大的自动化测试功能, 并且许多Rails开发人员使用 TDD和BDD方法 并且使用更强大的测试框架,比如 rspec and cucumber.

尽管我们可以轻松地将自动化测试添加到Rails应用程序中, 我继承或加入的项目中,有很多项目确实存在这样的问题,这让我感到不快和惊讶 no 编写测试——或者,充其量,很少. 尽管有很多关于你的测试应该有多全面的争论, 很明显,至少 some 每个应用程序都应该存在自动化测试.

这是一条经验法则, 应该为控制器中的每个操作至少编写一个高级集成测试. If, 在未来的某个时候, 开发人员扩展或修改代码, 或者升级Ruby或Rails版本, 这个测试框架将为他们提供一种明确的方法来验证应用程序的基本功能是否正常工作. 这种方法的另一个好处是,它清晰地描述了应用程序所提供的全部功能类型.

常见Rails编程错误#8:阻塞对外部服务的调用

Rails服务的第三方提供商倾向于通过包装其api的gem将其服务集成到您的应用程序中. 但是,如果您的外部服务中断或开始运行非常慢,会发生什么呢?

以避免阻塞这样的电话, 而不是在正常处理请求期间直接在Rails应用程序中调用这些服务, 在可行的地方, 将它们移动到某种后台作业队列服务. Rails应用程序中用于此目的的一些流行gem包括:

在不实际或不可行的情况下,将处理委托给后台作业队列, 当外部服务出现故障或遇到问题时,您需要确保应用程序具有足够的错误处理和故障转移准备,以应对这些不可避免的情况. 您还应该在没有外部服务的情况下测试应用程序(可能通过断开应用程序服务器与网络的连接),以验证它不会导致任何意想不到的后果.

常见的Rails编程错误#9:与现有的数据库迁移结婚

Rails’ 数据库迁移机制 允许您创建指令,以自动添加和删除数据库表和行. 因为包含这些迁移的文件是以顺序方式命名的, 您可以从一开始就播放它们,将空数据库与生产相同的模式中. 这是管理应用程序数据库模式的细粒度更改并避免Rails问题的好方法.

虽然它在项目开始时确实运行良好, 随着时间的流逝, 数据库创建过程可能需要更长的时间才能完成, 迁移可能是错误的, 乱插, 或者是从使用相同数据库服务器的其他Rails应用程序引入的.

默认情况下,Rails在一个名为 db/schema.rb. 该文件通常在数据库迁移运行时更新. The schema.rb 文件甚至可以在不存在迁移时通过运行 rake db:模式:转储 task. 一个常见的Rails错误是将新迁移签入到源库中, 没有相应的更新 schema.rb file.

当迁移无法控制并且运行时间过长时, 或者不再正确创建数据库, 您不应该害怕清除旧的迁移目录, 转储一个新模式, 然后继续. 建立一个新的开发环境需要一个 rake db:模式:负载 而不是 rake db: migrate 这是大多数开发者所依赖的. 有关此主题的其他信息请参见 Rails Guide.

常见Rails编程错误#10:将敏感信息检入源代码存储库

Rails框架使创建不受多种攻击影响的安全应用程序变得容易. 其中一些是通过使用秘密令牌来保护与浏览器的会话来完成的. 这样的令牌存储在 配置/秘密.yml,我们的应用程序从生产服务器的环境变量中读取令牌.

以前版本的Rails中包含了token 配置/初始化/ secret_token.rb, 哪一个经常被错误地签入到源代码存储库与您的应用程序的其余部分, and, 任何有权访问存储库的人都可以轻易地危及应用程序的所有用户.

确保您的存储库配置文件(e.g., .gitignore for git (用户)排除带有令牌的文件. 然后,您的生产服务器可以从环境变量或机制中获取令牌 dotenv gem provides.

教程结束

Rails是一个强大的框架,它隐藏了许多构建健壮的web应用程序所必需的丑陋细节. 虽然这使得Rails web应用程序的开发速度更快, 开发人员应该注意我们发现的潜在设计和编码错误. 这将使您的应用程序更容易扩展和维护.

注意那些可能使应用程序变慢、不可靠或不安全的问题. 研究框架并确保完全理解体系结构是很重要的, design, 以及您在整个开发过程中所做的编码权衡,以帮助确保高质量和高性能的应用程序.

了解基本知识

  • MVC架构背后的主要逻辑是什么?

    模型、视图、控制器(MVC)是一种关注分离的体系结构. 模型保存主要应用程序对象的数据. 视图应该只包含将数据转换为用户界面的逻辑. 控制器响应系统的主要事件, 通常与应用程序路由绑定, 这最终会导致更新的模型和视图.

  • 如何在Ruby on Rails中使用helper?

    Ruby on Rails helper通常是为了在整个应用程序中共享代码块而编写的函数. 有内置的和自定义的帮助程序, 这些帮助程序就像应用程序代码库中的函数一样被使用.

  • RubyGems的目的是什么?

    RubyGems是Ruby的包管理器. 包包含用于在应用程序中修改或扩展功能的代码, 并且可能包括文档和实用程序.

  • 如何在Rails中运行测试?

    Rails可以灵活地运行所有测试或特定测试,从而使测试变得简单. bin/rails test命令是启动这些测试的方法. 如果没有提到特定的测试,则将运行应用程序的所有测试.

  • 什么是Rails中的日志文件?

    Rails日志文件包含应用程序在运行时输出的信息. 日志信息将被限制在日志输出级别, 从高级错误到详细的调试信息.

  • 什么是Rails服务?

    Rails服务是一个对象,它允许开发人员将应用程序的业务逻辑与控制器和模型分离. 这种分离通常允许更简单的控制器和模型逻辑.

  • 为什么在Rails中需要迁移?

    Rails迁移允许定义封装的数据库模式更改,并可能将其存储在版本控制系统中. 一旦与团队中的其他开发人员共享, 可以运行这些迁移以通过命令执行更新数据模式.

  • Ruby on Rails有多安全?

    Ruby on Rails有一个开箱即用的默认安全框架,可以提供基本的保护,防止常见的攻击向量.

聘请Toptal这方面的专家.
Hire Now
布莱恩VanLoo的头像
布莱恩VanLoo

Located in 纽伯里公园,美国

成员自 2014年4月2日

作者简介

Brian是一名自由软件开发人员,他在企业界提供技术价值. 具有从嵌入式系统到大规模web应用程序的开源技术的专业知识.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

Stitch Fix

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® community.