Converting method to scope in Rails 3
I'd like to convert this method to a scope in rails so I could call something like Batch.all_completed and it would return all batches that met the criteria in the method:
def all_completed?
articles.with_status('Completed').count >= project.articles_per_week
end
The class definition of the batch that this will be run on is this:
class Batch < ActiveRecord::Base
has_many :articles
belongs_to :project
Is there a way to do this simply? with_status
is a named scope, and开发者_Python百科 articles_per_week
is a method not a field. Here is the SQL output from the left side:
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')
Thanks!
[Edit 2]
Given that articles_per_week is not a field but a method, you'll have to give up scope chainability - scopes boiled down are just AREL that generates a SQL statement, and there's no way to bridge the gap between the instance method and the database. At this point, things actually get easier and you can just make the whole thing a class method, with a call to #reject at the end to drop the ones that don't meet the count criteria:
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
And, you still get minimal DB hits with this (2). But, as noted calling #reject converts your output to a basic Array of objects rather than a chainable ActiveRecord collection.
Not sure why I had to convert article count to an integer, but so it was.
I've left my previous versions in, as I think they provide good additional info for other scenarios.
[Edit 1]
Here's a version you can put as a scope on Batch, so that it's chainable to other scopes.
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")
I noted in testing this that it can have an impact on some of the standard ActiveRecord methods. For instance #size doesn't work because on ActiveRecord that is translated into a SELECT COUNT(*), and that won't work here due to the missing projects.articles_per_week in the selected fields.
[Original]
My first answer here.
I'm not sure if this qualifies for your request of "simply", but it accomplishes the task in one SQL statement.
Article.includes([:batch => :project]).where(:status => 'Completed').group(:batch_id).having("count(articles.id) >= projects.articles_per_week").map(&:batch)
This assumes Rails 3 and the following model setups:
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
Incidentally, here's my test data. The structure above correctly pulled batches 1 and 2 and ignored batch 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');
精彩评论