棘手的Ruby / Rails循环和查询逻辑。建议需要。 SQL连接或组?

时间:2014-02-01 23:34:44

标签: sql ruby-on-rails ruby postgresql ruby-on-rails-4

我在Ruby,Rails和SQL(以及一般的编程)中仍然非常环保,但我知道基础知识。这是我的小谜题涉及酒窖组织应用程序:

我有模特。葡萄酒 - 具有属性:名称和:葡萄酒,葡萄酒 - 葡萄酒所属,用户 - 拥有许多葡萄酒,位置 - 葡萄酒的酒窖位置,这是一个字符串属性:名称和瓶子 - 每个瓶子都有用户记录,葡萄酒和位置ID。这是我对模型,模式,控制器和我想要使用的视图的要点: https://gist.github.com/mpron/8725840

这就是我想要做的事情:

  • 按葡萄酒名称
  • 按字母顺序显示用户葡萄酒的行
  • 如果酒的瓶子存在于多个位置,我想显示相同葡萄酒名称的多行(如果在3个不同的位置有相同葡萄酒名称的瓶子,那么我想为每个位置显示三个单独的行)
  • 我需要在每个行的每个位置显示葡萄酒瓶的数量(对于具有相同wine.name的瓶子来说,瓶子'.count')。

到目前为止,这是我粗略的,稍微伪代码的解决方案:

- if @bottles.count > 0
  You have 
  = @bottles.count
  bottles
  %table.table
    %tr
      %th Location
      %th Vintage
      %th Name
      %th Winery
      %th Quantity
      %th Actions
    - @wines.each do |wine|
      - @locations each do |loc|
        - if wine.@bottles.each exists in loc
          %tr
            %td
              = "#{loc}"
            %td
              = "#{wine.vintage}"
            %td
              = "#{wine.name}"
            %td
              = "#{wine.winery.name}"
            %td
              = "#{loc.bottles.where(name: wine.name).count}"
            %td
              = link_to "Delete", bottle_path(bottle), :method => "delete", :data => {:confirm => "Are you sure?"}, :class => "btn btn-danger btn-sm"

这也在gist以及其他相关文件中 我的问题是: 1.有效查询数据库的最佳方法是什么? 2.这属于视图,还是应该存在于其他地方,也许是部分?

如果有更有效的方式对此进行建模,我愿意对此进行大幅改进。我只是不知道足够的SQL来弄清楚如何使用GROUP BY或JOIN之类的东西来使这个工作得很好。一位朋友提出类似Location.joins(:bottles).where(name: wine.name)之类的内容,但后来又认定可能导致表现不佳。

(这是个人项目,不是家庭作业或任何东西。我只是难倒。)

2 个答案:

答案 0 :(得分:1)

我对你的问题的初步答案是与更好的关联。重构您的关联结构。例如,删除belongs_to :user与瓶子的关联:

class Bottle < ActiveRecord::Base

  belongs_to :wine
  # belongs_to :user
  belongs_to :location

end

在葡萄酒模型中添加belongs_to

class Wine < ActiveRecord::Base
  belongs_to :user
  belongs_to :winery
  has_many :bottles
  #has_many :locations
end

并向用户添加has_many through:

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
  :recoverable, :rememberable, :trackable, :validatable
  has_many :wines
  has_many :bottles, through :wines
end

您必须相应地调整列,以摆脱不需要的外键,并添加您需要的任何新键。您不需要has_many through的显式键,因为Rails会为您完成工作。 user -> wine -> bottles将此视为一个不错的例子:Getting count by drilling down through relations

这将使您能够拉动用户:

@user = User.first

您将自动获得可以通过以下方法访问的联接:

my_wines = @user.wines # wines in a user's cellar
my_bottles = @user.bottles # bottles in a user's cellar

这些都返回表示AR对象数组的ActiveRecord关系。然后,您可以访问这些对象,如:

my_bottles.each do |bottle|
   puts bottle.name
   puts bottle.location
end

我实际上需要了解有关使用.joins方法的更多信息,因为我很早就学会了has_many through我甚至没有意识到它正在为我做加入。至少在has_many through上做一些教程,你可以在模型和辅助方法上找到任何东西。我在评论中发布的CodeSchool.com链接是一个巨大的帮助。一个月25美元但值得每分钱。

.joins适用于需要将信息放在不具有您定义的显式关联的单独表中的情况。或者当您想从两个表中提取信息时,只需要提取与某个用户相关的信息。为了性能,我会注意在循环内做任何类型的SQL请求。我没有在这里看到任何东西,但只是一个抬头。

通过删除所有嵌套循环并在关联和辅助方法中布置逻辑,您的视图将更加清晰。可以这样想,视图中的三重嵌套循环基本上是一个分层对象。您应该利用您的关联并构建一些封装该逻辑的方法,这样您在视图中只有一个.each语句。重构您的关联,如果您仍然没有看到更简单的方法来封装您的视图逻辑,请告诉我,我可以给出更多指导。

答案 1 :(得分:0)

@Beartech让我想到如何利用has_many通过:协会,所以在重新研究之后,我想出了我的解决方案:

wine.rb

class Wine < ActiveRecord::Base

  belongs_to :winery
  has_many :bottles
  has_many :locations, through: :bottles
end

user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
  :recoverable, :rememberable, :trackable, :validatable

  has_many :wines, through: :bottles
  has_many :bottles
end

location.rb

class Location < ActiveRecord::Base
    has_many :bottles
  has_many :wines, through: :bottles

end

bottle.rb

class Bottle < ActiveRecord::Base

  belongs_to :wine
  belongs_to :user
  belongs_to :location

end

users_controller.rb的部分

  def show
    @user_wines = @user.wines.order('wines.name')
    @bottles = @user.bottles
  end

show.html.haml

  %table.table
    %tr
      %th Location
      %th Vintage
      %th Name
      %th Winery
      %th Quantity
      %th Actions
    - @user_wines.each do |wine|
      - wine.locations.each do |loc|
        %tr
          %td= loc
          %td= wine.vintage
          %td= wine.name
          %td= wine.winery.name
          %td= loc.bottles.where(:wine_id => wine.id).count

这将贯穿数据库中葡萄酒的一个更窄的子集,以及数据库中更小的位置子集,这样我的嵌套循环就不会查询那么多。