我是Rails初学者并且要了解它我正在构建一个简单的时间跟踪应用程序。我想使用来自许多具有嵌套信息的表的大量信息来填充管理员的仪表板。
查询数据库以请求一家公司的所有数据查看所有客户,项目,任务,调整和分钟的仪表板的最佳做法是什么?
以下是数据的结构:
公司 has_many客户
客户端 belongs_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最佳实践的好书或资源?
答案 0 :(得分:1)
这会产生与你相同的
@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
答案 1 :(得分:1)
使用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
这是一个可怕的Law of Demeter违规,如果这些事情中的任何一点在任何时候发生变化,无论是在结构还是命名方面,我们都必须修复此代码。我并不确定如何在不引入大量抽象的情况下解决这个问题。
关于一本书,有很多,但老实说,我不认为Rails世界已经找到了什么构成了最好的ActiveRecord实践(事实上,社区的很大一部分认为几乎所有的ActiveRecord实践都只是太可怕了 - 我主要在那个营地里。
但是,如果您想要上述内容(即使用#includes
来表示热切加载关联),那么指南就是查找此类信息的好地方。我也非常喜欢this博客和视频。
答案 2 :(得分:0)
尝试类似
的内容 Client.includes(
:company =>{:projects=>:tasks})
以上所有内容都应该连接(通过has_one,has_many,belongs_to) 希望这有帮助!