【问题标题】:Converting method to scope in Rails 3在 Rails 3 中将方法转换为范围
【发布时间】:2011-06-27 21:22:23
【问题描述】:

我想将此方法转换为 rails 中的范围,这样我就可以调用 Batch.all_completed 之类的东西,它会返回满足方法中条件的所有批次:

def all_completed?
  articles.with_status('Completed').count >= project.articles_per_week
end

将运行的批处理的类定义如下:

class Batch < ActiveRecord::Base
  has_many :articles
  belongs_to :project

有没有办法简单地做到这一点? with_status 是一个命名范围,articles_per_week 是一个方法而不是一个字段。这是左侧的 SQL 输出:

SELECT "articles".* FROM "articles" join jobs on jobs.article_id = articles.id join statuses on statuses.id = jobs.status_id WHERE ("articles".batch_id = XXX) AND (statuses.description = 'Completed')

谢谢!

【问题讨论】:

  • 抱歉,我不清楚 - 你想从 Batch#all_completed 返回什么?
  • 我希望它返回满足方法中条件的所有 Batch 实例

标签: sql ruby-on-rails ruby-on-rails-3 named-scope


【解决方案1】:

[编辑 2]

鉴于 articles_per_week 不是一个字段而是一个方法,您将不得不放弃作用域可链接性 - 归结起来的作用域只是生成 SQL 语句的 AREL,没有办法桥接实例方法和数据库之间的差距。在这一点上,事情实际上变得更容易了,你可以把整个事情变成一个类方法,最后调用 #reject 来删除不符合计数条件的那些:

class Batch < ActiveRecord::Base
  belongs_to :project
  has_many :articles

  def self.all_completed
    joins(:articles).
    includes(:project).
    where("articles.status = 'Completed'").
    group("batches.id").
    select("batches.*, count(articles.id) as article_count").
    reject {|batch| batch.article_count.to_i < batch.project.articles_per_week}
  end
end

而且,您仍然可以通过此 (2) 获得最少的数据库命中。但是,如前所述,调用 #reject 会将您的输出转换为基本的对象数组,而不是可链接的 ActiveRecord 集合。

不知道为什么我必须将文章计数转换为整数,但确实如此。

我保留了以前的版本,因为我认为它们为其他场景提供了很好的附加信息。

[编辑 1]

这是一个可以作为 Batch 范围的版本,以便它可以链接到其他范围。

scope :all_completed,
  joins(:articles, :project).
  where("articles.status = 'Completed'").
  having("count(articles.id) >= projects.articles_per_week").
  group("batches.id").
  select("batches.*, projects.articles_per_week")

我在测试中注意到它可能会对一些标准的 ActiveRecord 方法产生影响。例如,#size 不起作用,因为在 ActiveRecord 上它被转换为 SELECT COUNT(*),并且由于所选字段中缺少 projects.articles_per_week 而在此处不起作用。

[原创]

我在这里的第一个答案。

我不确定这是否符合您“简单”的要求,但它在一个 SQL 语句中完成了任务。

Article.includes([:batch => :project]).where(:status => 'Completed').group(:batch_id).having("count(articles.id) >= projects.articles_per_week").map(&:batch)

这假设 Rails 3 和以下模型设置:

class Batch < ActiveRecord::Base
  has_many :articles
  belongs_to :project
end

class Project < ActiveRecord::Base
  has_many :batches
end

class Article < ActiveRecord::Base
  belongs_to :batch
end

顺便说一下,这是我的测试数据。上面的结构正确地提取了第 1 批和第 2 批,而忽略了第 3 批。

-- phpMyAdmin SQL Dump
-- version 3.3.2deb1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Jun 27, 2011 at 07:13 PM
-- Server version: 5.1.41
-- PHP Version: 5.3.2-1ubuntu4.9

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

--
-- Database: `stack_development`
--

-- --------------------------------------------------------

--
-- Table structure for table `articles`
--

DROP TABLE IF EXISTS `articles`;
CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `batch_id` int(11) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `status` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=16 ;

--
-- Dumping data for table `articles`
--

INSERT INTO `articles` (`id`, `batch_id`, `name`, `status`, `created_at`, `updated_at`) VALUES
(1, 1, 'Test 1 Article', 'Completed', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(2, 1, 'Test 2 Article', 'Completed', '2011-06-27 22:40:36', '2011-06-27 22:40:36'),
(3, 1, 'Test 3 Article', 'Completed', '2011-06-27 22:40:42', '2011-06-27 22:40:42'),
(4, 1, 'Test Article 4', 'Completed', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(5, 1, 'Test Article 5', 'Pending', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(6, 2, 'Test 1 Article', 'Completed', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(7, 2, 'Test 2 Article', 'Completed', '2011-06-27 22:40:36', '2011-06-27 22:40:36'),
(8, 2, 'Test 3 Article', 'Completed', '2011-06-27 22:40:42', '2011-06-27 22:40:42'),
(9, 2, 'Test Article 4', 'Pending', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(10, 2, 'Test Article 5', 'Pending', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(11, 3, 'Test 1 Article', 'Completed', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(12, 3, 'Test 2 Article', 'Completed', '2011-06-27 22:40:36', '2011-06-27 22:40:36'),
(13, 3, 'Test 3 Article', 'Pending', '2011-06-27 22:40:42', '2011-06-27 22:40:42'),
(14, 3, 'Test Article 4', 'Pending', '2011-06-27 22:38:55', '2011-06-27 22:38:55'),
(15, 3, 'Test Article 5', 'Pending', '2011-06-27 22:38:55', '2011-06-27 22:38:55');

-- --------------------------------------------------------

--
-- Table structure for table `batches`
--

DROP TABLE IF EXISTS `batches`;
CREATE TABLE IF NOT EXISTS `batches` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `project_id` int(11) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=4 ;

--
-- Dumping data for table `batches`
--

INSERT INTO `batches` (`id`, `project_id`, `name`, `created_at`, `updated_at`) VALUES
(1, 1, 'Test 1 Batch', '2011-06-27 22:38:11', '2011-06-27 22:38:11'),
(2, 2, 'Test 2 Batch', '2011-06-27 22:38:11', '2011-06-27 22:38:11'),
(3, 3, 'Test 3 Batch', '2011-06-27 22:38:11', '2011-06-27 22:38:11');

-- --------------------------------------------------------

--
-- Table structure for table `projects`
--

DROP TABLE IF EXISTS `projects`;
CREATE TABLE IF NOT EXISTS `projects` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `articles_per_week` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=4 ;

--
-- Dumping data for table `projects`
--

INSERT INTO `projects` (`id`, `name`, `articles_per_week`, `created_at`, `updated_at`) VALUES
(1, 'Test 1 project', 3, '2011-06-27 22:37:11', '2011-06-27 22:37:11'),
(2, 'Test 2 Project', 3, '2011-06-27 22:37:21', '2011-06-27 22:37:21'),
(3, 'Test 3 Project', 3, '2011-06-27 22:37:21', '2011-06-27 22:37:21');

【讨论】:

  • 这看起来是一个非常好的开始。但是,有一个小症结:articles_per_week 是一种方法。它看起来像这样:def articles_per_week (!articles_per_period.blank? &amp;&amp; !frequency_period.blank?) ? articles_per_period * (1.week.to_i/frequency_period).to_i : nil end
  • 呃,我在上面看到了 - 在第一次阅读(以及第二次、第三次等)时错过了它。已更新以满足此要求。
  • 院子——这太棒了。谢谢你的帮助!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-13
  • 1970-01-01
  • 2012-02-18
  • 2011-08-19
  • 2023-03-29
  • 2011-11-28
  • 2019-07-27
相关资源
最近更新 更多