I'm having this scope function inside my model
# models/Post.rb
def self.filtered (params)
unless params[:year].blank? && params[:month].blank?
year = params[:year].to_i
month = params[:month].to_i
return where(created_at: DateTime.new(year, month, 1).beginning_of_day..DateTime.new(year, month, -1).end_of_day)
end
self
end
# controllers/posts_controller.rb
#posts = Post.filtered(params)
Which basically returns all archived posts of specific year and month
SELECT `posts`.* FROM `posts`
WHERE (`posts`.`created_at` BETWEEN '2017-10-01 00:00:00' AND '2017-10-31 23:59:59')
I'm trying to write a test for this method to make sure that a post is was created in the requested year and month, how can I do this?
# spec/models/post_spec.rb
describe '.filtered' do
let!(:older) { FactoryGirl.create(:post, created_at: 1.month.ago) } # this post should not appear in the list
let!(:newer) { FactoryGirl.create(:post, created_at: Time.zone.now) } # this post should appear in the list
it 'is within specific year and month' do
expect(Post.filtered({year: Date.today.strftime("%Y"), month: Date.today.strftime("%m")}).map { |post| post.created_at }).to be ???
end
end
Use the include matcher to verify a record is included in the result set.
expect(Post.filtered({year: Date.today.strftime("%Y"), month: Date.today.strftime("%m")}).to include(newer)
Use #contain_exactly to match elements when order should be disregarded.
# spec/models/post_spec.rb
describe '.filtered' do
let!(:older) { FactoryGirl.create(:post, created_at: 1.month.ago) } # this post should not appear in the list
let!(:newer) { FactoryGirl.create(:post, created_at: Time.zone.now) } # this post should appear in the list
it 'is within specific year and month' do
expect(Post.filtered({year: Date.today.strftime("%Y"), month: Date.today.strftime("%m")}).map { |post| article.created_at }).to contain_exactly(newer)
end
end
By the way, instead of creating a class method like what you did here, you might want to consider a scope so it can be chained with other scopes.
Related
I'm not very experienced with Rails and RSpec and often have troubles with writing tests. So, my next trouble is that I don't know how to test order in model's relationship properly.
Let's say I have simple model like this:
class Kitchen < ActiveRecord::Base
has_many :orders, -> { order(completed_at: :desc) }
end
And simple test for that model:
require 'rails_helper'
RSpec.describe Kitchen, :type => :model do
before { #kitchen = FactoryGirl.create(:kitchen) }
subject { #kitchen }
describe "orders" do
before do
#kitchen.orders.build(description: "Test description 1",
completed_at: 1.day.ago.localtime)
#kitchen.orders.build(description: "Test description 2",
completed_at: Time.now)
end
it "should be sorted by completion date in descending order" do
expect(#kitchen.orders.first.completed_at).to be > #kitchen.orders.last.completed_at
end
end
end
As a result I have got the error:
Failure/Error: expect(#kitchen.orders.first.completed_at).to be > #kitchen.orders.last.completed_at
expected: > Fri, 15 May 2015 12:21:54 UTC +00:00
got: Thu, 14 May 2015 12:21:54 UTC +00:00
Any help will be appreciated.
You are using build. This does not persist to the database, so when you call #kitchen.orders.first, you are not hitting the database, just getting back the first one you created, which is the wrong one.
Use create instead, then call #kitchen.reload to refresh from the database.
How about the following:
it {
is_expected.to have_many(:orders).order( completed_at: :desc )
}
I would do something like this:
RSpec.describe Kitchen, :type => :model do
let(:kitchen) { FactoryGirl.create(:kitchen) }
describe 'orders' do
let(:yesterday) { 1.day.ago }
let(:today) { Time.now }
before do
# `create` actually saves the objects into the database
kitchen.orders.create(description: '1', completed_at: today)
kitchen.orders.create(description: '2', completed_at: yesterday)
end
# `(true)` forces a reload of the associated objects
subject(:orders) { kitchen.orders(true) }
it 'should be sorted by completion date in descending order' do
expect(orders.map(&:completed_at)).to eq [yesterday, today]
end
end
end
I have the following spec for the controller in simple ActiveRecord search feature:
Spec:
it "returns the records that match the given due date" do
create(:task, due_date: '2013-01-01')
create(:task, due_date: '2014-01-01')
get :search, 'filter' => { due_date: '2013-01-01' }
expect(assigns(:tasks)).to \
eq Task.search('filter' => { due_date: '2013-01-01' })
end
The model and controller are simple:
Model:
def self.search(params)
result = self.all #I know this is a bad idea, but starting simple.
params.each do |field, criteria|
if field.match(/due_date|completed_date/) && criteria != nil
result = result.where("DATE(#{field}) = ?", criteria)
end
end
result
end
Controller action:
def search
#tasks = Task.search(params['filter'])
#output from when the spec runs below
#puts params -> {"filter"=>{"due_date"=>"2013-01-01"}, \
# "controller"=>"tasks", \
# "action"=>"search"}
#puts params['filter] -> {"due_date"=>"2013-01-01"}
#puts #tasks.inspect -> just the one record
render 'index'
end
The spec fails, but it appears that it fails because the controller is returning both objects, while Task.search(...) is returning only the object with the specified value for due_date, as expected.
Here is the error message (edited for length):
2) TasksController GET #search returns the records that
match the given due date
Failure/Error: expect(assigns(:tasks)).to
eq Task.search('filter' => { due_date: '2013-01-01' })
expected: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">,
#<Task id: 2, due_date: "2014-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
got: #<ActiveRecord::Relation
[#<Task id: 1,
due_date: "2013-01-01 00:00:00",
completed_date: "2013-12-22 03:57:37">]>
You would assume that since the model apparently works (as evidenced by this result and a separate model spec that passes) that there is something wrong with the controller, but the controller is dead simple. I also have a feature spec incorporating the same controller that submits a form, triggers the search action and looks at the output, and the output only includes the one, correct record.
Am I missing something about how assigns works, making a dumb mistake or other?
It was option B, dumb mistake.
The model method takes the value of the filter element of the params hash as an argument, not the fake params hash I need to send to GET #searchin the line above the expectation. Or more clearly maybe, replace:
expect(assigns(:tasks)).to eq Task.search('filter' => { due_date: '2013-01-01' })
with
expect(assigns(:tasks)).to eq Task.search(due_date: '2013-01-01')'
In a RSpec spec file I have the following test
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.find(:all,
:select => ["*,(abs(rating - current_user.rating)) as player_rating"],
:order => "player_rating",
:limit => 5)
# test that matched_players array returns what it is suppose to
end
How would I complete this to test that matched_players is returning the correct users.
I think you should first introduce some test users to the test DB (using for example a Factory) and afterwards see that the test is returning the correct ones.
Also it would make more sense to have a method in your model that would return the matched users.
For example:
describe "Player matching" do
before(:each) do
#user1 = FactoryGirl.create(:user, :rating => 5)
...
#user7 = FactoryGirl.create(:user, :rating => 3)
end
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.matched_players
matched_players.should eql [#user1,#user3,#user4,#user5,#user6]
end
end
Your model shouldn't know about your current user (the controllers know about this concept)
You need to extract this as a method on the User class otherwise there's no point in testing it, i.e. why test logic that isn't even in your app code?
The function that gets the matched players doesn't need to know about the current user, or any user for that matter, just the rating.
To test it, create a bunch of User instances, call the method, and see that the result is a list of the correct user instances you expect.
models/user.rb
class User < ActiveRecord::Base
...
def self.matched_players(current_user_rating)
find(:all,
select: ["*,(abs(rating - #{current_user_rating)) as match_strength"],
order: "match_strength",
limit: 5)
end
...
end
spec/models/user_spec.rb
describe User do
...
describe "::matched_players" do
context "when there are at least 5 users" do
before do
10.times.each do |n|
instance_variable_set "#user#{n}", User.create(rating: n)
end
end
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4.2)
matched_players.should == [#user4, #user5, #user3, #user6, #user2]
end
context "when multiple players have ratings close to the given rating and are equidistant" do
# we don't care how 'ties' are broken
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4)
matched_players[0].should == #user4
matched_players[1,2].should =~ [#user5, #user3]
matched_players[3,4].should =~ [#user6, #user2]
end
end
end
context "when there are fewer than 5 players in total" do
...
end
...
end
...
end
I'm trying to write an app that calculates sick/vacation days and how much an employee has available in either category. Here's my trouble:
In my view, the duration equation works and shows the right numbers, but I've put the math in the view, which I know is bad. But when I try to use the duration equation in my employee class (so I can move the math out of the view) it doesn't work, and I think that it's because duration is saving as 'nil' for some reason. I don't know why it's doing that, as everything else has been saving in the database with whatever information I input into the form.
Maybe it's because duration isn't inputted manually in the form, but rather reacts to the date-range?
Here's where I want to call duration in the employee model to get the math out of the view:
def remaining_vacation_days
vacation_days - #furlough.duration if #furlough.description == "Vacation"
end
def remaining_sick_days
sick_days - #furlough.duration if #furlough.description == "Sick"
end
Here's the model where duration is defined:
class Furlough < ActiveRecord::Base
attr_accessible :duration # and other stuff
belongs_to :employee
validates_presence_of :duration # and other stuff
def duration
only_weekdays(date_from..date_to) - psb_holidays
end
def only_weekdays(range)
range.select { |d| (1..5).include?(d.wday) }.size
end
def psb_holidays
Holidays.between(date_from, date_to, :us, :observed).size
end
end
What's tripping me out is that in the console this is what I see:
1.9.3-p0 :008 > ryan = Furlough.find(18)
Furlough Load (0.3ms) SELECT "furloughs".* FROM "furloughs" WHERE "furloughs"."id" = ? LIMIT 1 [["id", 18]]
=> #<Furlough id: 18, duration: nil, date_from: "2013-12-20", note: "Christmas vacation!", created_at: "2013-05-08 14:33:03", updated_at: "2013-05-08 14:34:07", employee_id: 16, description: "Vacation", date_to: "2013-12-29">
See, it's nil, but then I get this:
1.9.3-p0 :009 > ryan.duration
=> 5
I'm at a loss.
You are supposed to use instance of the class, not the class itself, thats why you are getting all those errors.
def sick_days_used
Furlough.duration if Furlough.description == "Sick"
end
should be :
def sick_days_used
#furlough.duration if #furlough.description == "Sick"
end
or
def sick_days_used
self.duration if self.description == "Sick"
end
if your are defining it in model
The attributes are attributes of a Furlough instance, not the Furlough class itself.
If you are going to use the methods as class methods then you need to add 'self' to the method definition:
def self.duration
...
end
Then you can call Furlough.duration.
The other way around (def duration) you are defining an instance method, which can only be called on an instance (an specifiic Furlogh instance).
I have a model called Article and what I want to do is show all of the Articles where the created_at date was for last week. I understand how to a rolling week (ie the last 7 days to now), such as this question.
scope :last_week, lambda { :conditions => { :created_at => 1.week.ago..DateTime.now.end_of_day } }
However, what I want to do is to find the Articles that were Sunday - Saturday of last week, regardless of what day the current day is.
Similar to John's answer, but I think this fits your situation a little better:
scope :last_week, lambda { { conditions: { created_at: last_week_range } } }
def self.last_week_range
1.week.ago.beginning_of_week(:sunday)..1.week.ago.end_of_week(:sunday)
end
Date.parse gives an awesome results being run on the weekday name:
require 'date'
# ⇒ true
Date.today
# ⇒ <Date: 2013-04-02 ((2456385j,0s,0n),+0s,2299161j)>
Date.parse('Sunday')
# ⇒ <Date: 2013-03-31 ((2456383j,0s,0n),+0s,2299161j)>
Date.parse('Wednesday')
# ⇒ <Date: 2013-04-03 ((2456386j,0s,0n),+0s,2299161j)>
So all you need is:
def last_weekend
d = Date.parse('Saturday')
d -= 7 if d > Date.today
Range.new d, d+1
end
Hope this helps.
Rails provides a helper method to get the beginning and end of a week for a given date. The method defaults to monday so you have to pass in sunday to override it.
scope :last_week, lambda { :conditions => { :created_at => Date.today.at_beginning_of_week(:sunday)..Date.today.at_end_of_week(:sunday) } }
I have usen Date.today as an example but you could have the scope have a date argument so you could apply any date to the scope.
Edit:
scope :last_week, lambda{ |date| {:conditions => ((date - 1.week).at_beginning_of_week(:sunday))..((date-1.week).at_end_of_week(:sunday)) }}
then just call the scope as
last_week(some_date)