Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

can detect basic n+1 query and use spec for testing

  • Loading branch information...
commit f97ffbec8ccf10891167d8fd17287db3ddee7c94 1 parent c740655
Richard authored
View
19 Rakefile
@@ -1,19 +1,11 @@
require 'rake'
-require 'rake/testtask'
+require 'spec/rake/spectask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
-task :default => :test
+task :default => :spec
-desc 'Test the bullet plugin.'
-Rake::TestTask.new(:test) do |t|
- t.libs << 'lib'
- t.libs << 'test'
- t.pattern = 'test/**/*_test.rb'
- t.verbose = true
-end
-
-desc 'Generate documentation for the bullet plugin.'
+desc 'Generate documentation for the sitemap plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'Bullet'
@@ -21,3 +13,8 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end
+
+desc "Run all specs in spec directory"
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_files = FileList['spec/spec_helper.rb', 'spec/**/*_spec.rb']
+end
View
112 lib/bullet.rb
@@ -2,14 +2,69 @@ module Bullet
class Association
class <<self
def start_request
- @@associations = {}
+ @@object_associations = {}
+ @@unpreload_associations = {}
+ end
+
+ def unpreload_associations
+ @@unpreload_associations
+ end
+
+ def has_klazz_association(klazz)
+ !@@klazz_associations[klazz].nil? and @@klazz_associations.keys.include?(klazz)
+ end
+
+ def klazz_association(klazz)
+ @@klazz_associations[klazz] || []
+ end
+
+ def define_association(klazz, associations)
+ puts "define association: #{klazz} => #{associations}"
+ @@klazz_associations ||= {}
+ @@klazz_associations[klazz] ||= []
+ @@klazz_associations[klazz] << associations
+
+ klazz.class_eval <<-END
+ Bullet::Association.klazz_association(self).each do |association|
+ origin_method_name = 'origin_' << association.to_s
+ new_method_name = association.to_s
+ alias_method origin_method_name, new_method_name
+
+ define_method(association.to_s) do
+ Bullet::Association.call_association(self, association)
+ self.send(origin_method_name)
+ end
+ end
+ END
+
+ # class <<klazz
+ # alias_method :origin_find_every, :find_every
+ #
+ # def find_every(options)
+ # puts "find every #{options}"
+ # records = origin_find_every(options)
+ # include_associations = merge_includes(scope(:find, :include), options[:include])
+ # if !include_associations.any? and Bullet::Association.has_klazz_association(records.first.class)
+ # records.each do |record|
+ # Bullet::Association.check_association(record)
+ # end
+ # end
+ # records
+ # end
+ # end
end
def add_association(object, associations)
- @@associations[object] = associations
+ puts "add association: #{object} => #{associations}"
+ @@object_associations[object] ||= []
+ @@object_associations[object] << associations
end
def call_association(object, associations)
+ puts "call assocation: #{object} => #{associations}"
+ if @@object_associations[object].nil? or !@@object_associations[object].include?(associations)
+ @@unpreload_associations[object.class] = associations
+ end
end
def end_request
@@ -21,60 +76,33 @@ def end_request
module ActiveRecord
module AssociationPreload
module ClassMethods
+ alias_method :origin_preload_associations, :preload_associations
+
def preload_associations(records, associations, preload_options={})
records = [records].flatten.compact.uniq
return if records.empty?
- records.each do |record|
- puts "add association"
- Bullet::Association.add_association(record, associations)
- end
- case associations
- when Array then associations.each {|association| preload_associations(records, association, preload_options)}
- when Symbol, String then preload_one_association(records, associations.to_sym, preload_options)
- when Hash then
- associations.each do |parent, child|
- raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
- preload_associations(records, parent, preload_options)
- reflection = reflections[parent]
- parents = records.map {|record| record.send(reflection.name)}.flatten.compact
- unless parents.empty?
- parents.first.class.preload_associations(parents, child)
- end
+ if records.count > 1
+ records.each do |record|
+ Bullet::Association.add_association(record, associations)
end
end
+ origin_preload_associations(records, associations, preload_options={})
end
end
end
end
+ActiveRecord::ActiveRecordError # An ActiveRecord bug
+
module ActiveRecord
module Associations
module ClassMethods
+ alias_method :origin_collection_reader_method, :collection_reader_method
+
def collection_reader_method(reflection, association_proxy_class)
- define_method(reflection.name) do |*params|
- puts "call association"
- Bullet::Association.call_association(self, reflection.name)
- force_reload = params.first unless params.empty?
- association = association_instance_get(reflection.name)
-
- unless association
- association = association_proxy_class.new(self, reflection)
- association_instance_set(reflection.name, association)
- end
-
- association.reload if force_reload
-
- association
- end
-
- define_method("#{reflection.name.to_s.singularize}_ids") do
- if send(reflection.name).loaded? || reflection.options[:finder_sql]
- send(reflection.name).map(&:id)
- else
- send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
- end
- end
+ origin_collection_reader_method(reflection, association_proxy_class)
+ Bullet::Association.define_association(self, reflection.name)
end
end
end
-end
+end
View
67 spec/bullet_spec.rb
@@ -0,0 +1,67 @@
+ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
+
+describe Bullet do
+ def setup_db
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :posts do |t|
+ t.column :name, :string
+ end
+
+ create_table :comments do |t|
+ t.column :name, :string
+ t.column :post_id, :string
+ end
+ end
+ end
+
+ def teardown_db
+ ActiveRecord::Base.connection.tables.each do |table|
+ ActiveRecord::Base.connection.drop_table(table)
+ end
+ end
+
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post
+ end
+
+ before(:all) do
+ setup_db
+
+ post = Post.create(:name => 'first')
+ Comment.create(:name => 'first', :post => post)
+ Comment.create(:name => 'second', :post => post)
+ # post.comments.create(:name => 'first')
+ # post.comments.create(:name => 'second')
+ post = Post.create(:name => 'second')
+ Comment.create(:name => 'third', :post => post)
+ Comment.create(:name => 'fourth', :post => post)
+ # post.comments.create(:name => 'third')
+ # post.comments.create(:name => 'fourth')
+ end
+
+ after(:all) do
+ teardown_db
+ end
+
+ it "should detect preload" do
+ Bullet::Association.start_request
+ Post.find(:all, :include => :comments).each do |post|
+ post.comments
+ end
+ Bullet::Association.unpreload_associations.should be_empty
+ Bullet::Association.end_request
+ end
+
+ it "should detect preload" do
+ Bullet::Association.start_request
+ Post.find(:all).each do |post|
+ post.comments
+ end
+ Bullet::Association.unpreload_associations.should_not be_empty
+ Bullet::Association.end_request
+ end
+end
View
8 spec/spec.opts
@@ -0,0 +1,8 @@
+--colour
+--format
+specdoc
+--reverse
+--timeout
+20
+--loadby
+mtime
View
4 spec/spec_helper.rb
@@ -0,0 +1,4 @@
+require 'rubygems'
+require 'spec/autorun'
+require 'active_record'
+require File.join(File.dirname(__FILE__), '../lib/bullet.rb')
View
48 test/bullet_test.rb
@@ -1,48 +0,0 @@
-require 'rubygems'
-require 'test/unit'
-require 'active_record'
-require 'active_record/associations'
-require File.join(File.dirname(__FILE__), '../lib/bullet')
-
-ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
-
-def setup_db
- ActiveRecord::Schema.define(:version => 1) do
- create_table :posts do |t|
- t.column :name, :string
- end
-
- create_table :comments do |t|
- t.column :name, :string
- t.column :post_id, :string
- end
- end
-end
-
-def teardown_db
- ActiveRecord::Base.connection.tables.each do |table|
- ActiveRecord::Base.connection.drop_table(table)
- end
-end
-
-class Post < ActiveRecord::Base
- has_many :comments
-end
-
-class Comment < ActiveRecord::Base
- belongs_to :post
-end
-
-class BulletTest < Test::Unit::Testcase
- def setup
- setup_db
- end
-
- def teardown
- teardown_db
- end
-
- def test_detect_preload
- assert true
- end
-end
Please sign in to comment.
Something went wrong with that request. Please try again.