Rails初学者 – 查询多个表以一次检索大量信息的最佳实践

我是Rails初学者并且要学习它我正在构建一个简单的时间跟踪应用程序。 我想用许多来自嵌套信息的表中的大量信息填充管理员的仪表板。

查询数据库以请求一家公司的所有数据查看所有客户,项目,任务,调整和分钟的仪表板的最佳做法是什么?

以下是数据的结构:

公司有很多客户

客户owner_to公司has_many项目

项目belongs_to客户端has_many任务

任务belongs_to项目has_many分钟

分钟belongs_to任务


这种数据结构可能非常糟糕。 我不知道。

数据的示例视图:


– 网站重新设计
—发展
—- 100分钟

我从这开始,但我很漂亮,但它可能完全倒退(用户属于公司):

@clients = Client.find_all_by_company_id(current_user.company_id) @clients.each do |client| project = Project.find_all_by_client_id(client.id) puts project.name project.each do |project| task = Task.find_all_by_project_id(project.id) puts task.name end end 

我想也可以问一个问题:是否有一本好书或资源完全描述了Rails ActiveRecord最佳实践?

使用includes方法急切加载关联。

指南中的示例

 Category.includes(:posts => [{:comments => :guest}, :tags]).find(1) 

根据你所说的,应该是:

 require 'active_record' require 'logger' # ===== Config ===== ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:' ActiveRecord::Base.logger = Logger.new $stdout ActiveSupport::LogSubscriber.colorize_logging = false # ===== Schema ===== ActiveRecord::Schema.define do self.verbose = false create_table :clients do |t| t.string :name t.integer :company_id end create_table :companies do |t| t.string :name end create_table :projects do |t| t.string :name t.integer :client_id end create_table :tasks do |t| t.string :name t.integer :project_id end create_table :minutes do |t| t.integer :quantity t.integer :task_id end end # ===== Classes ===== class Company < ActiveRecord::Base has_many :clients end class Client < ActiveRecord::Base belongs_to :company has_many :projects end class Project < ActiveRecord::Base belongs_to :client has_many :tasks end class Task < ActiveRecord::Base belongs_to :project has_many :minutes end class Minute < ActiveRecord::Base belongs_to :task end # ===== Data ===== Company.create! name: 'Activision' do |company| company.clients.build name: 'Robert Kotick' do |client| client.projects.build name: 'Website Redesign' do |project| project.tasks.build name: 'Development' do |task| task.minutes.build quantity: 100 end end end end # ===== Querying and displaying ===== company = Company.find_by_name 'Activision' clients = Client.includes(projects: {tasks: :minutes}).where(company_id: company.id) print "\n----- The query makes four requests, regardless of how much data you have. -----\n\n" clients.inspect # do this to force loading since AR queries are lazy print "\n----- some representation of the data (notice no queries while iterating through) -----\n\n" clients.each do |client| puts client.name client.projects.each do |project| puts "-- #{project.name}" project.tasks.each do |task| puts "--- #{task.name}" task.minutes.each do |minute| puts "---- #{minute.quantity}" end end end end # ===== Output ===== # >> D, [2012-09-12T00:01:42.755414 #72855] DEBUG -- : (0.7ms) select sqlite_version(*) # >> D, [2012-09-12T00:01:42.755890 #72855] DEBUG -- : (0.2ms) CREATE TABLE "clients" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "company_id" integer) # >> D, [2012-09-12T00:01:42.756327 #72855] DEBUG -- : (0.1ms) CREATE TABLE "companies" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)) # >> D, [2012-09-12T00:01:42.756728 #72855] DEBUG -- : (0.1ms) CREATE TABLE "projects" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "client_id" integer) # >> D, [2012-09-12T00:01:42.757122 #72855] DEBUG -- : (0.1ms) CREATE TABLE "tasks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "project_id" integer) # >> D, [2012-09-12T00:01:42.757531 #72855] DEBUG -- : (0.1ms) CREATE TABLE "minutes" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "quantity" integer, "task_id" integer) # >> D, [2012-09-12T00:01:42.906877 #72855] DEBUG -- : (0.0ms) begin transaction # >> D, [2012-09-12T00:01:42.909242 #72855] DEBUG -- : SQL (0.5ms) INSERT INTO "companies" ("name") VALUES (?) [["name", "Activision"]] # >> D, [2012-09-12T00:01:42.934937 #72855] DEBUG -- : SQL (24.7ms) INSERT INTO "clients" ("company_id", "name") VALUES (?, ?) [["company_id", 1], ["name", "Robert Kotick"]] # >> D, [2012-09-12T00:01:42.936110 #72855] DEBUG -- : SQL (0.1ms) INSERT INTO "projects" ("client_id", "name") VALUES (?, ?) [["client_id", 1], ["name", "Website Redesign"]] # >> D, [2012-09-12T00:01:42.937001 #72855] DEBUG -- : SQL (0.1ms) INSERT INTO "tasks" ("name", "project_id") VALUES (?, ?) [["name", "Development"], ["project_id", 1]] # >> D, [2012-09-12T00:01:42.937767 #72855] DEBUG -- : SQL (0.1ms) INSERT INTO "minutes" ("quantity", "task_id") VALUES (?, ?) [["quantity", 100], ["task_id", 1]] # >> D, [2012-09-12T00:01:42.938005 #72855] DEBUG -- : (0.0ms) commit transaction # >> D, [2012-09-12T00:01:42.939882 #72855] DEBUG -- : Company Load (0.1ms) SELECT "companies".* FROM "companies" WHERE "companies"."name" = 'Activision' LIMIT 1 # >> # >> ----- The query makes four requests, regardless of how much data you have. ----- # >> # >> D, [2012-09-12T00:01:42.940458 #72855] DEBUG -- : Client Load (0.1ms) SELECT "clients".* FROM "clients" WHERE "clients"."company_id" = 1 # >> D, [2012-09-12T00:01:42.943272 #72855] DEBUG -- : Project Load (0.1ms) SELECT "projects".* FROM "projects" WHERE "projects"."client_id" IN (1) # >> D, [2012-09-12T00:01:42.943919 #72855] DEBUG -- : Task Load (0.1ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."project_id" IN (1) # >> D, [2012-09-12T00:01:42.944520 #72855] DEBUG -- : Minute Load (0.1ms) SELECT "minutes".* FROM "minutes" WHERE "minutes"."task_id" IN (1) # >> # >> ----- some representation of the data (notice no queries while iterating through) ----- # >> # >> Robert Kotick # >> -- Website Redesign # >> --- Development # >> ---- 100 

这是一个可怕的Demeter违规法则 ,如果这些事情中的任何一个在任何时候发生变化,无论是在它们的结构还是命名中,我们都必须修复这段代码。 如果不引入大量抽象,我真的不确定如何处理。

关于一本书,有很多,但老实说,我不认为Rails世界已经弄清楚什么构成了最好的ActiveRecord实践(事实上,社区的很大一部分认为几乎所有的ActiveRecord实践都很糟糕 – 我主要在那个营地。

但是如果你想要上面的内容,即使用#includes来预测负载关联,那么指南就是找到这样的信息的好地方。 我也非常喜欢这个博客和video。

这产生了你所拥有的相同

 @clients = current_user.company.clients @clients.each do |client| projects = client.projects # puts project.name # makes no sense here projects.each do |project| project.tasks.each do |task| puts task.name end end end 

尝试类似的东西

  Client.includes( :company =>{:projects=>:tasks}) 

以上所有都应该连接(通过has_one,has_many,belongs_to)希望这有帮助!