I'm using RSpec and FactoryGirl for testing my models and I'm stuck at "highest_priority" method which can't be seen by RSpec for some reason.
Here's the method itself:
models/task.rb
class Task < ActiveRecord::Base
#some stuff
def self.highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end
And when I run task_spec.rb
require 'spec_helper'
describe Task do
it "returns highest priority" do
last_task = FactoryGirl.build(:task, priority: "5")
last_task.highest_priority
expect(last_task(:priority)).to eq("6")
end
end
I get the following error:
When I'm calling this method in my controller like this
def create
#task = current_user.tasks.build(task_params)
#task.highest_priority
#task.complete = false
respond_to do |format|
if #task.save
format.js
else
format.js
end
end
end
And the method looks like
def highest_priority
self.maximum(:priority).to_i + 1
end
I'm getting
First of all, you better use ActiveRecord's maximum instead of ordering and then picking one, you'll avoid the instance initialization and get a number directly from the query
Task.maximum(:priority)
this could be put in a class method like this
def self.maximum_priority
Task.maximum(:priority) || 0 # fall back to zero if no maximum exists
end
Then for the second half which is updating the method, i would create an instance method for that, and using the class method
def set_maximum_priority
self.priority = self.class.maximum_priority + 1
self
end
Note that I returned self at the end for chainability
Then your action would become something like this
def create
#task = current_user.tasks.build(task_params).set_maximum_priority
#task.complete = false
...
end
You need to create the method as an instance method of Task model. Like below :
class Task < ActiveRecord::Base
#some stuff
def highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end
Related
Can somebody help me with rspec testing method call in Service Object?
class UserEntitiesController < ApplicationController
def create
#result = UserEntities::Create.new(params).call
return render '/422.json.jbuilder', status: :unprocessable_entity unless #result
end
here is the service objects:
module UserEntities
class Create
attr_accessor :params
def initialize(params)
#params = params
end
def call
#user_entity = UserEntity.new(user_entity_params)
set_time
if #user_entity.save
#user_entity
else
error_result
end
end
private
def error_result
false
end
def user_entity_params
#params.require(:user_entity).permit(:information,
:destroy_option,
:reviews)
end
def set_time
if #params[:available_days].present?
#user_entity.termination = Time.now + #params[:available_days].days
end
end
end
end
I tried to find information how to do this, but there are not so many.
Also i read some
You can certainly write a unit test to test the Service Object standalone
In this case, create a file spec/services/user_entities/create_spec.rb
describe UserEntities::Create do
let(:params) { #values go here }
context ".call" do
it "create users" do
UserEntities::Create.new(params).call
# more test code
end
# more tests
end
end
Later in the controller tests, if you are planning to write such, you do not need to test UserEntities::Create instead you can just mock the service object to return the desired result
describe UserEntitiesController do
before do
# to mock service object in controller test
allow(UserEntities::Create).to receive(:new)
.and_return(double(:UserEntities, call: "Some Value"))
end
# controller tests go here
end
As a supplement to #bibin answer.
If you want to mock some instance's method renturn:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_return("some value")
if you want to raise a eror:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_raise("boom")
I have the following personal class to work on an arrangement for the shopping cart, but when I try to call a method of this class in my controller it indicates an undefined method add_cesta.
This is my class
class Carro
attr_reader :cesta
def initialize
#cesta = []
end
def add_cesta(articulo)
#cesta << articulo
end
end
and this is my controller
class TiendaController < ApplicationController
def index
#titulo = "Bienvenido a la Tienda"
#articulos = Articulo.all.order("nombre").page(params[:page]).per_page(4)
end
def quienes_somos
#titulo = "Bienvenido a la Tienda"
end
def contacto
#titulo = "Bienvenido a la Tienda"
end
def anadir_producto
#articulo = Articulo.find(params[:id])
#carro = sesion_carrito
#carro.add_cesta(#articulo)
flash[:info] ="Producto aƱadido #{#articulo.nombre}"
redirect_to inicio_url
end
def ver_carro
#carro = session[:carro]
end
def vaciar_carrito
session[:carro] = nil
flash[:info] = "Carrito vacio"
redirect_to inicio_url
end
private
def sesion_carrito
session[:carro] ||= Carro.new
end
end
I believe you didn't actually set the instance variable to a cart.
Perhaps the session[:carro] contains something else than a cart?
Could you post the full error message please as this would clarify your problem quite a bit.
For now try setting:
#carro = Carro.new
And afterwards refactor the session part a bit.
-- Edit --
Seems like the session variable is a string instead of an actual object.
Try storing the cart_id in your session and retrieve it later on.
def ver_carro
#carro ||= (Carro.find(session[:carro_id]) || Carro.new)
end
with something like that.
You don't have access to the method within the controller as it's defined in a separate class. You would need to explicitly provide access by requiring the file within the controller class. Put the path to the actual file within the quotes below. See similar question here: Including a Ruby class from a separate file
require 'file_path/carro'
class TiendaController < ApplicationController
I'm using the Prawn gem to write to PDF. I have started an action to write the PDF but I don't understand how to use my data in the right way. I have:
def download
#bid = Bid.find(params[:bid_id])
#title = #bid.bid_title.gsub(/\s+/, "")
Prawn::Document.generate("#{#title}.pdf") do
text #bid.client_name
end
end
Where I add the text, the Bid is nil. How do I use the #bid that I created before in the block below?
It is often useful to dug into source code to understand how all the magic works.
If we consider Prawn source code, we can see that in method self.generate(filename, options = {}, &block) our block is transmitted to Prawn::Document.new method. Hence, we shall consider Prawn::Document initialize method. There we can see the following code:
if block
block.arity < 1 ? instance_eval(&block) : block[self]
end
#arity is a number of block arguments.
# block[self] is a block.call(self)
If we simplify Prawn source code, we can mock this situation in order to understand it better:
module Prawn
class Document
def self.generate(filename, &block)
block.arity < 1 ? instance_eval(&block) : block[self]
end
end
end
class A
def initialize
#a = 1
end
def foo
qwe = 1
Prawn::Document.generate("foobar") do
p #a
p qwe
p instance_variables
end
end
end
A.new.foo
# Output:
nil # #a
1 # qwe
[] # there is no instance_variables
But if we provide an argument for our block, another condition in generate will be called (block[self] instead of instance_eval):
module Prawn
class Document
def self.generate(filename, &block)
block.arity < 1 ? instance_eval(&block) : block[self]
end
end
end
class A
def initialize
#a = 1
end
def foo
qwe = 1
Prawn::Document.generate("foobar") do |whatever|
p #a
p qwe
p instance_variables
end
end
end
A.new.foo
# Output
1 # #a
1 # qwe
[:#a] # instance_variables
So in your situation this code will work I think:
def download
#bid = Bid.find(params[:bid_id])
#title = #bid.bid_title.gsub(/\s+/, "")
Prawn::Document.generate("#{#title}.pdf") do |ignored|
text #bid.client_name
end
end
or
def download
bid = Bid.find(params[:bid_id])
title = #bid.bid_title.gsub(/\s+/, "")
Prawn::Document.generate("#{title}.pdf") do
text bid.client_name
end
end
Your problem is that Prawn::Document.generate evaluates the block in the context of a Prawn::Document instance. This means that instance variables in the block will be resolved as instance variables of the Prawn::Document object, since that is self in the context of the block.
To make this work, use local variables instead of (or in addition to) instance variables.
I have a class which is responsible for dealing with some response from payments gateway.
Let's say:
class PaymentReceiver
def initialize(gateway_response)
#gateway_response = gateway_response
end
def handle_response
if #gateway_response['NC_STATUS'] != '0'
if order
order.fail_payment
else
raise 'LackOfProperOrder'
# Log lack of proper order
end
end
end
private
def order
#order ||= Order.where(id: #gateway_response['orderID']).unpaid.first
end
end
In payload from payment I've NC_STATUS
which is responsible for information if payment succeed and orderID which refers to Order ActiveRecord class byid`.
I would like to test behavior(in rspec):
If PaymentReceiver receives response where NC_STATUS != 0 sends fail_payment to specific Order object referred by orderID.
How you would approach to testing this ? I assume that also design could be bad ...
You have to make refactorization to remove SRP and DIR principles violations.
Something below I'd say:
class PaymentReceiver
def initialize(response)
#response = response
end
def handle_response
if #response.success?
#response.order.pay
else
#response.order.fail_payment
end
end
end
# it wraps output paramteres only !
class PaymentResponse
def initialize(response)
#response = response
end
def order
# maybe we can check if order exists
#order ||= Order.find(#response['orderID'].to_i)
end
def success?
#response['NCSTATUS'] == '0'
end
end
p = PaymentReceiver.new(PaymentResponse({'NCSTATUS' => '0' }))
p.handle_response
Then testing everything is easy.
I have a code which calculates a parameter in the create action and update action differently. Here expiry_time is the column in the db and expiry_duration is a virtual parameter.
class SomeController
def create
params[:model][:expiry_time] = Time.now + params[:model][:expiry_duration].to_i
#model = Model.new(params[:model])
if #model.save
redirect_to ..
else
render ..
end
end
def update
#model = Model.find(params[:id])
params[:model][:expiry_time] = #model.created_at + params[:model][:expiry_duration].to_i
if #model.update_params(params[:model])
redirect_to ..
else
render ..
end
end
end
I was thinking of moving the calculation part to model in a before_save function. Should I check for the id there to decide if it is a new record or an existing one like this?
class Model
before_save :update_expiry_time
def update_expiry_time
start_time = id ? created_at : Time.now
expiry_time = start_time + expiry_duration.to_i
end
def expiry_duration
expiry_time - created_at
end
end
What do you think?
use new_record?, or use :on => :create to call a different funciton