I'm building a Rails (4.1.0) app that runs a poll. each poll has n Matchups with n Seats. Here are my models:
class Matchup < ActiveRecord::Base
has_many :seats, dependent: :destroy
def winning_seat
seats.sort { |a,b| a.number_of_votes <=> b.number_of_votes }.last
end
end
class Seat < ActiveRecord::Base
belongs_to :matchup
validates :matchup, presence: true
validates :number_of_votes, presence: true
def declare_as_winner
self.is_winner = true
self.save
end
end
My specs for Matchup and Seat pass without issue. At the end of a poll, I need to display the winner. I am using a Sidekiq worker to handle the end of the poll. It does many things, but here's the code in question:
class EndOfPollWorker
include Sidekiq::Worker
def perform(poll_id)
poll = Poll.where(:id poll_id)
poll.matchups.each do |matchup|
# grab the winning seat
winning_seat = matchup.winning_seat
# declare it as a winner
winning_seat.declare_as_winner
end
end
end
The spec for this worker doesn't pass:
require 'rails_helper'
describe 'EndOfPollWorker' do
before do
#this simple creates a matchup for each poll question and seat for every entry in the matchup
#poll = Poll.build_poll
end
context 'when the poll ends' do
before do
#winners = #poll.matchups.map { |matchup| matchup.seats.first }
#losers = #poll.matchups.map { |matchup| matchup.seats.last }
#winners.each do |seat|
seat.number_of_votes = 1
end
#poll.save!
#job = EndOfPollWorker.new
end
it 'it updates the winner of each matchup' do
#job.perform(#poll.id)
#winners.each do |seat|
expect(seat.is_winner?).to be(true)
end
end
it 'it does not update the loser of each matchup' do
#job.perform(#poll.id)
#losers.each do |seat|
expect(seat.is_winner?).to be(false)
end
end
end
end
end
end
When I run this spec, I get:
EndOfPollWorker when poll ends it updates the winner of each matchup
Failure/Error: expect(seat.is_winner?).to be(true)
expected true
got false
My specs for the Seat and Matchup models pass just fine. I cut a lot of the test code out, so excuse any mismatched tags, assume that's not the problem!
Also, when the workers actually run in development mode, the seats.is_winner attribute isn't actually updated.
Thanks
Sidekiq has nothing to do with your problem. You're directly calling perform so the issue is with rspec and activerecord. For instance, pull the code out of the perform method and put it directly in the spec, it should still fail.
I suspect the instances are stale and need to be #reload'd from the database to pick up the changes done in #perform.
Related
This is my first project in rails, and for some reason I fail to create my first unit test for my controller.
Basically, I have a main object Election, and each election may contain many voters.
The voters are created from a comma separated list of emails.
In this test, I want to test several lists of emails, to be sure that they are ingested correctly.
But for a reason I can't really grasp, my Voter model is not detected by my controller test.
So here is the related part of the code :
voters_controller_test.rb
require 'test_helper'
class VotersControllerTest < ActionController::TestCase
test "should add new voters" do
assert_difference('Voters.count', 2) do
post :create, voter: {election_id: 1, email_list: "me#me.fr, you#you.com"}
end
end
end
voter.rb
class Voter < ActiveRecord::Base
attr_accessor :email_list
belongs_to :election
validates :email, presence: true, :email => true
validates_uniqueness_of :email, :scope => [:election_id]
end
and the controller, voters_controller.rb
class VotersController < ApplicationController
def index
#election = Election.find(params[:election_id])
end
def create
#election = Election.find(params[:election_id])
emails = voter_params[:email_list].squish.split(',')
emails.each { |email| #voter = #election.voters.create(:email =>email) }
redirect_to election_voters_path(#election)
end
private
def voter_params
params.require(:voter).permit(:email_list)
end
end
I should probably mention that my application works fine, and that only the test is failing.
The exact error message is :
Run options: --seed 24993
# Running:
E.
Finished in 0.098560s, 20.2922 runs/s, 10.1461 assertions/s.
1) Error:
VotersControllerTest#test_should_add_new_voters:
NameError: uninitialized constant VotersControllerTest::Voters
/home/jll/Documents/01_perso/00_myelections/test/controllers/voters_controller_test.rb:6:in `block in <class:VotersControllerTest>'
This it is my very first ruby test, I heavily inspired myself from the rails testing tutorial.
Could you please provide me some insight on what I do wrong?
Thanks!
You're trying to assert the difference on the Voters model instead of the Voter model. This is what the code should look like.
assert_difference('Voter.count', 2) do
...
end
Remember, models will bear the singular version of the resource name while controllers will bear the plural name. E.g. The model is Voter while the controller is VotersController.
In my model, I dynamically create some methods based on database records:
class Job < ActiveRecord::Base
belongs_to :job_status
# Adds #requisition?, #open?, #paused?, #closed?
class_eval do
JobStatus.all.each do |status|
unless method_defined? "#{status.name.downcase}?"
define_method("#{status.name.downcase}?") do
job_status_id == status.id
end
end
end
end
end
class JobStatus < ActiveRecord::Base
has_many :jobs
end
The job_statuses table contains some seed data, so is not going to be frequently changing, but in case I ever need to add new statuses, I don't have to add more code to get a boolean method for the new status.
However, I am not sure how to test these methods, because when rspec starts the job_statuses table is obviously empty, and when the JobStatus objects are created, Job gets initialized, but since no objects exist yet, it doesn't create any methods, and my tests fail because the methods don't exist.
Note that I am using rspec with spork & guard, and using database-cleaner with the truncation strategy (as per Railscast #257, since I'm using Selenium), so that probably complicates matters.
The solution I came up with was to abstract the creation of runtime methods out into a library file, and then in my test file, remove and redeclare my class before each test, and reload the actual class (and blueprints) at the end of the suite:
describe AssociationPredicate do
before(:all) do
["Continuous", "Standard"].each { |type| JobType.create!(:job_type => type) }
["Requisition", "Open", "Paused", "Closed"].each { |status| JobStatus.create!(:job_status => status) }
end
after(:all) do
DatabaseCleaner.clean_with :truncation, :only => %w( job_types job_statuses )
# Reload Job model to remove changes
Object.send(:remove_const, 'Job')
load 'job.rb'
load 'support/blueprints.rb'
end
before(:each) do
Object.send(:remove_const, 'Job')
# Redefine Job model for testing purposes
class Job < ActiveRecord::Base
belongs_to :job_type
belongs_to :job_status
has_many :job_applications
end
end
it "should add methods when included" do
Job.send(:association_predicate, :job_type)
job.should respond_to(:continuous?)
job.should respond_to(:standard?)
end
end
This way, I create a basic class for each test, add the runtime methods as necessarily, and return to the actual class when I'm done.
Try with enumerize gem. This make your status field like enumerator and build the "#{status.name.downcase}?" for your models. This gem came with it's own rspec-matchers making easiest your unit test.
I am building a survey app where, based on ratings I need certain things to happen.
Basically, if a survey is submitted with a total rating under 15, we need to notify a supervisor. That's easy enough with mailers, but I can't seem to access the rating data in an after_create method.
My model has 5 fields named A,B,C,D, and E which are integers and they hold the rating data in the form.
I have tried :notation I have tried self.notation, I've tried after_create(service) service.notation and nothing works - the email never gets sent because it doesn't realize that the rating is lower than 15.
I also have a checkbox with similar issues. In the database it appears as "true" but before it is saved it usually shows up as 1 so testing for the correct value is tricky. Similar to the code below, I can't access it's value either. I've listed all the various methods I've tried with no success.
Obviously these are not all existent in the model at the same time, they are listed below as examples of what I have attempted
How do I access these data values in an after_create call?!
class Service < ActiveRecord::Base
after_create :lowScore
def lowScore
if(A+B+C+D+E) < 15 #does not work
ServiceMailer.toSupervisor(self).deliver
end
end
def lowScore
if(self.A+self.B+self.C+self.D+self.E) < 15 #does not work either
ServiceMailer.toSupervisor(self).deliver
end
end
#this does not work either!
def after_create(service)
if service.contactMe == :true || service.contactMe == 1
ServiceMailer.contactAlert(service).deliver
end
if (service.A + service.B + service.C + service.D + service.E) < 15
ServiceMailer.toSupervisor(service).deliver
ServiceMailer.adminAlert(service).deliver
end
end
Figured out a solution.
In model.rb:
after_create :contactAlert, :if => Proc.new {self.contactMe?}
after_create :lowScore, :if => Proc.new {[self.A, self.B, self.C, self.D, self.E].sum < 15}
def contactAlert
ServiceMailer.contactAlert(self).deliver
end
def lowScore
ServiceMailer.adminAlert(self).deliver
ServiceMailer.toSupervisor(self).deliver
end
The key was using the Proc.new to do the tests for conditions.
Do debug:
class Service < ActiveRecord::Base
after_create :low_score
def low_score
# raise (A+B+C+D+E).inspect # uncomment this line to debug your code
# it will raise exception with string containing (A+B+C+D+E). See what is result this line in your console tab where rails server started
# Or you can see result in your browser for this raise
ServiceMailer.toSupervisor(self).deliver if (A+B+C+D+E) < 15
end
end
I need some solution to make following functionality in my RoR 3 site:
Site needs a user rating system, where users get points for performing some actions (like getting points for answering questions on stackoverflow).
The problems are:
1) I need ability to re-assign amount of points for some actions (not so often, but I can't restart Mongrel each time I need to re-assign, so in-code constants and YAML don't suit)
2) I can't use simple Active Record, because on 5000 users I'll do too many queries for each user action, so I need caching, and an ability to reset cache on re-assignment
3) I would like to make it without memcached or something like this, cause my server hardware is old enough.
Does anyone know such solution?
What about something like this ?
##points_loaded = false
##points
def load_action_points
if (File.ctime("setting.yml") < Time.now) || !##points_loaded
##points = YAML::load( File.open( 'setting.yml' ) )
##points_loaded = true
else
##points
end
or use A::B and cache the DB lookups
class ActionPoints < ActiveRecord::Base
extend ActiveSupport::Memoizable
def points
self.all
end
memoize :points
end
You also cache the points in the User model, something like this .. pseudocode...
class User < A::B
serialize :points
def after_save
points = PointCalculator(self).points
end
end
and....
class PointCalculator
def initialize(user)
##total_points = 0
user.actions.each do |action|
p = action.times * ActionPoints.find_by_action("did_something_cool").points
##total_points = ##total_points + p
end
end
def points
##total_points
end
end
I've found some simple solution, but it works only if you have one RoR server instance:
class Point < ActiveRecord::Base
##cache = {}
validates :name, :presence => true
validates :amount, :numericality => true, :presence => true
after_save :load_to_cache, :revaluate_users
def self.get_for(str)
##cache = Hash[Point.all.map { |point| [point.name.to_sym, point] }] if ##cache == {} #for cache initializing
##cache[str.to_sym].amount
end
private
def load_to_cache
##cache[self.name.to_sym] = self
end
def revaluate_users
#schedule some background method, that will update all users' scores
end
end
If you know a solution for multiple server instances without installing memcached, I will be very glad to see it.
Suppose you have an ActiveRecord::Observer in one of your Ruby on Rails applications - how do you test this observer with rSpec?
You are on the right track, but I have run into a number of frustrating unexpected message errors when using rSpec, observers, and mock objects. When I am spec testing my model, I don't want to have to handle observer behavior in my message expectations.
In your example, there isn't a really good way to spec "set_status" on the model without knowledge of what the observer is going to do to it.
Therefore, I like to use the "No Peeping Toms" plugin. Given your code above and using the No Peeping Toms plugin, I would spec the model like this:
describe Person do
it "should set status correctly" do
#p = Person.new(:status => "foo")
#p.set_status("bar")
#p.save
#p.status.should eql("bar")
end
end
You can spec your model code without having to worry that there is an observer out there that is going to come in and clobber your value. You'd spec that separately in the person_observer_spec like this:
describe PersonObserver do
it "should clobber the status field" do
#p = mock_model(Person, :status => "foo")
#obs = PersonObserver.instance
#p.should_receive(:set_status).with("aha!")
#obs.after_save
end
end
If you REALLY REALLY want to test the coupled Model and Observer class, you can do it like this:
describe Person do
it "should register a status change with the person observer turned on" do
Person.with_observers(:person_observer) do
lambda { #p = Person.new; #p.save }.should change(#p, :status).to("aha!)
end
end
end
99% of the time, I'd rather spec test with the observers turned off. It's just easier that way.
Disclaimer: I've never actually done this on a production site, but it looks like a reasonable way would be to use mock objects, should_receive and friends, and invoke methods on the observer directly
Given the following model and observer:
class Person < ActiveRecord::Base
def set_status( new_status )
# do whatever
end
end
class PersonObserver < ActiveRecord::Observer
def after_save(person)
person.set_status("aha!")
end
end
I would write a spec like this (I ran it, and it passes)
describe PersonObserver do
before :each do
#person = stub_model(Person)
#observer = PersonObserver.instance
end
it "should invoke after_save on the observed object" do
#person.should_receive(:set_status).with("aha!")
#observer.after_save(#person)
end
end
no_peeping_toms is now a gem and can be found here: https://github.com/patmaddox/no-peeping-toms
If you want to test that the observer observes the correct model and receives the notification as expected, here is an example using RR.
your_model.rb:
class YourModel < ActiveRecord::Base
...
end
your_model_observer.rb:
class YourModelObserver < ActiveRecord::Observer
def after_create
...
end
def custom_notification
...
end
end
your_model_observer_spec.rb:
before do
#observer = YourModelObserver.instance
#model = YourModel.new
end
it "acts on the after_create notification"
mock(#observer).after_create(#model)
#model.save!
end
it "acts on the custom notification"
mock(#observer).custom_notification(#model)
#model.send(:notify, :custom_notification)
end