RSpec - failing test unless local variable is called from pry point - ruby-on-rails

I have the following code:
## teams_controller.rb
def destroy
ActiveRecord::Base.transaction do
team_admins = team.team_admins
binding.pry
team.destroy!
team_admins.each(&:update_team_admin_role_if_needed!)
end
respond_with_200(team, serializer: V1::User::Management::TeamSerializer)
end
And the corresponding spec to ensure the last line of the above code fires:
## teams_controller_spec.rb
it 'demotes team admins to employees when needed' do
team_admin_account = create(:account)
admin_team_membership = create(:team_membership, team: #team, admin: true, account: team_admin_account)
team_admin_account.update!(role: Role.team_admin)
expect { process_destroy(team_id: #team.slug) }
.to change { team_admin_account.reload.role }
.from(Role.team_admin)
.to(Role.employee)
end
When I use the above code in my application it works as expected, however the spec fails as the account apparently never has their role updated:
expected `team_admin_account.reload.role` to have changed from #<Role id: 4, add_to_first_user_in_organisation: false, title: "Team admin", created_at: "2020-01-03 09:04:28", updated_at: "2020-01-03 09:04:28", management: false, cms_access: false> to #<Role id: 3, add_to_first_user_in_organisation: false, title: "Employee", created_at: "2020-01-03 09:04:28", updated_at: "2020-01-03 09:04:28", management: false, cms_access: false>, but did not change
When I hit the pry point in my spec and quit out straight away, the spec fails. Likewise when there is no pry point.
However when I enter team_admins at the pry point (which returns the one team_admin I create in my spec) and then quit out of the spec, the spec passes and the account has their role updated.
Anyone have any idea why manually calling team_admins makes my spec pass?
Thanks in advance
EDIT:
The following change to the code also makes the spec pass:
def destroy
ActiveRecord::Base.transaction do
team_admins = team.team_admins
puts team_admins ## <---- Adding this makes the spec pass
team.destroy!
team_admins.each(&:update_team_admin_role_if_needed!)
end
respond_with_200(team, serializer: V1::User::Management::TeamSerializer)
end

team_admins is an AssociationRelation from team. This does not execute a query until referenced, such as with each or puts.
team.destroy! is called before team_admins.each is called. So when team_admins.each executes there is no more team and thus no team_admins. You should be able to verify this by watching logs/test.log and looking at the queries and when they are executed.
Congratulations, you've found a bug. Execute team_admins.each before destroying team.

Related

ROR : Workflow gem : I want to implement dynamic states from database

I am currrently working on a project that has to implement dynamic workflow.
Dynamic: I store workflow's states in database table called wf_steps and the workflow gem has to create states for a particular workflow from the database
For that I am trying to use the workflow gem. You can see how it initializes states and corresponding events in the gem's github-page.
My code:
class SpsWorkflow < ActiveRecord::Base
belongs_to :user
has_many :wf_steps
include Workflow
workflow do
# binding.pry
# read wf-states from the database
# for now let event be same for all the states
self.wf_steps.each do |step|
state step.title.to_sym do
event :assign, transitions_to: :assigning
event :hire, transitions_to: :hiring
event :not_hire, transitions_to: :not_hiring
end
end
end
end
Expectation and Problem encountered:
I expected in the code block below the term self.wf_steps would return my SpsWorkflow's instance/collection. However the self keyword returns #<Workflow::Specification:0x000000063e23e8 #meta={}, #states={}> when I use binding.pry inside the workflow method's block ( I commented in the code )
# read wf-states from the database
# for now let event be same for all the states
self.wf_steps.each do |step|
state step.title.to_sym do
Need you help, thanks
EDIT:
I also tried storing the instance in a variable and using the variable inside the block passing to the workflow method call.
class SpsWorkflow < ActiveRecord::Base
include Workflow
sps_instance = self
But I got the instance of the class SpsWorkflow like
SpsWorkflow(id: integer, workflow_state: string, assigned_to: integer, title: string, description: string, organization_id: integer, user_id: integer, created_at: datetime, updated_at: datetime)
but I want
#<SpsWorkflow id: 1, workflow_state: "step1", assigned_to: nil, title: "Software Engineer", description: "Hire best engineer", organization_id: nil, user_id: 1, created_at: "2015-08-08 00:58:12", updated_at: "2015-08-08 00:58:12">
You have used:
workflow do
self.something
end
self in the context of this block will refer to the WorkflowSpecification. If you really want access to the instance of SpsWorkflow, you may have to pass it into the block or assign it to a different variable and use it there.
I finally solved it using a activerecord callback
class SpsWorkflow < ActiveRecord::Base
include Workflow
after_initialize do
sps_instance = self
SpsWorkflow.workflow do
# read wf-states as well as events from the database
sps_instance.wf_steps.each do |step|
state step.title.to_sym do
event :assign, transitions_to: :step2
event :hire, transitions_to: :hire
event :not_hire, transitions_to: :not_hiring
end
end
end
end
belongs_to :user
has_many :wf_steps
end

Rails 4 after_initialize not getting called on find

In a rails 4 (currently 4.0.10) application model I was having a problem with a method not defined for NilClass error in a view, and so I decided to try to solve by setting a sometime nil to an empty string in the model's after_initialize thusly:
def after_initialize
...
self.biography ||= ""
end
because, according to the docs here http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html , "after_initialize callback is triggered for each object that is found and instantiated by a finder"
yet, it appears that after_initialize never gets called and so the error persists, as tested in the rails console:
2.1.1 :001 > Presenter.find(2)
=> #<Presenter id: 2, mediahub_id: 2, name: "Craig", asset_id: nil, biography: nil, status: "active", created_at: "2015-08-12 23:00:34",
updated_at: "2015-08-12 23:00:34">
as you can see, biography is still nil after the find. What am I doing wrong? :)

Why Does A Unit Test For destroy_all fail with let?

So I have this model code:
def self.cleanup
Transaction.where("created_at < ?", 30.days.ago).destroy_all
end
and this rspec unit test:
describe 'self.cleanup' do
before(:each) do
#transaction = Transaction.create(seller:item.user, buyer:user, item:item, created_at:6.weeks.ago)
end
it 'destroys all transactions more than 30 days' do
Transaction.cleanup
expect(#transaction).not_to exist_in_database
end
end
with these factories:
FactoryGirl.define do
factory :transaction do
association :seller, factory: :user, username: 'IAMSeller'
association :buyer, factory: :user, username: 'IAmBuyer'
association :item
end
factory :old_transaction, parent: :transaction do
created_at 6.weeks.ago
end
end
using this rspec custom matcher:
RSpec::Matchers.define :exist_in_database do
match do |actual|
actual.class.exists?(actual.id)
end
end
When I change the spec to this:
describe 'self.cleanup' do
let(:old_transaction){FactoryGirl.create(:old_transaction)}
it 'destroys all transactions more than 30 days' do
Transaction.cleanup
expect(old_transaction).not_to exist_in_database
end
end
the test fails. I also tried manually creating a transaction and assigning it to :old_transaction with let() but that makes the test fail too.
Why is it that it only passes when I use an instance variable in the before(:each) block?
Thanks in advance!
EDIT: FAILED OUTPUT
1) Transaction self.cleanup destroys all transactions more than 30 days
Failure/Error: expect(old_transaction).not_to exist_in_database
expected #<Transaction id: 2, seller_id: 3, buyer_id: 4, item_id: 2, transaction_date: nil, created_at: "2014-02-26 10:06:30", updated_at: "2014-04-09 10:06:32", buyer_confirmed: false, seller_confirmed: false, cancelled: false> not to exist in database
# ./spec/models/transaction_spec.rb:40:in `block (3 levels) in <top (required)>'
let is lazy loaded. So in your failing spec this is the order of events:
Transaction.cleanup
old_transaction = FactoryGirl.create(:old_transaction)
expect(old_transaction).not_to exist_in_database
So the transaction is created after you attempt to clean up.
There are multiple options for you:
Don't use let for this
Unless you have other specs that you want to tell other devs:
I fully intend for all of these specs to reference what should be the exact same object
I personally feel, that you're better off inlining the transaction.
it do
transaction = FactoryGirl.create(:old_transaction)
Transaction.cleanup
expect(transaction).not_to exist_in_database
end
Use the change matcher
This is my personal choice as it clearly demonstrates the intended behavior:
it do
expect{
Transaction.cleanup
}.to change{ Transaction.exists?(old_transaction.id) }.to false
end
This works with let as the change block is run before AND after the expect block. So on the first pass the old_transaction is instantiated so it's id can be checked.
Use before or reference old_transaction before your cleanup
IMO this seems odd:
before do
old_transaction
end
it do
old_transaction # if you don't use the before
Transaction.clean
# ...
end
Use let!
The let! is not lazy loaded. Essentially, it's an alias for doing a normal let, then calling it in a before. I'm not a fan of this method (see The bang is for surprise for details why).
I think you've just accidentally typoed in a ":"
Try this spec:
describe 'self.cleanup' do
let(:old_transaction){FactoryGirl.create(:old_transaction)}
it 'destroys all transactions more than 30 days' do
Transaction.cleanup
expect(old_transaction).not_to exist_in_database
end
end

Factory girl and rspec unit testing

I am using factory_girl_rails and rspec and run into trouble,
raises the folloing error
1) WebsiteLink when link is external
Failure/Error: website_link.external should be false
expected #<FalseClass:0> => false
got #<WebsiteLink:100584240> => #<WebsiteLink id: nil, website_id: nil, link: nil, external: nil, checked: nil, click_count: nil, transition_count: nil, created_at: nil, updated_at: nil, link_description: nil>
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.
It is my code in spec.rb file
it "when link is external" do
website = FactoryGirl.create(:website,site_address: "www.socpost.ru")
website_link = FactoryGirl.create(:website_link, link: "www.google.com", website: website)
website_link.external should be true
end
The factory_girl factories
FactoryGirl.define do
factory :website do
sequence(:site_name){ |i| "Facebook#{i}" }
sequence(:site_address){ |i| "www.facebook_#{i}.com" }
sequence(:website_key){ |i| (1234567 + i).to_s }
end
factory :website_link do
sequence(:link){ |i| "www.facebook.com/test_#{i}" }
external false
checked false
click_count 1
transition_count 1
link_description "Hello"
website
end
end
Since I think it's helpful to understand why you received the error you received, here's an explanation:
You're statement had four expressions separated by spaces: website_link.external, should, be and false
Ruby evaluates these from right to left
false is trivial
be is interpreted as a method call with false as an argument
should is interpreted as a method with the result of be as an argument.
should is interpreted relative to subject, since the method wasn't sent to a specific object
Given the error you received, subject was either explicitly set to be WebsiteLink or that was the argument to describe for a parent of the example and thus the implicit subject
website_link.external never got evaluated because the error occurred prior to that point
You forgot to use the dot.
it "when link is external" do
website = FactoryGirl.create(:website,site_address: "www.socpost.ru")
website_link = FactoryGirl.create(:website_link, link: "www.google.com", website: website)
website_link.external.should be true (note the dot between external and should)
end
Give it a try.

Ruby on rails: Session/current_user, db-insert

I'm having problems with an insert to the database. First an explanation of my little Blog app.
The models: Users och Posts. http://pastie.org/2694864
A post have columns: title, body, user id
3 controllers:
Session, Application (with current_user) and PostController: http://pastie.org/2695386
My loggin session seems to work but when a logged in user shoult write a post the database doesn't recognize any user_id. It's just set to nil. rails console:
=> #<Post id: 17, title: "hello", body: "hello world", created_at: "2011-10-14 14:54:25", updated_at: "2011-10-14 14:54:25", user_id: nil>
I guess it's in the post controller line 88 this should be fixed but I can't figure it out.
I have also tried:
#post = Post.new(params[:post], :user_id => session[:user_id])
But the user_id stills sets to nil!
This is my first app so I would be really greatful for detaild answears.
Tanx!
The problem is that you're passing Post.new two arguments (two hashes in this case), but it only takes one argument. Try this:
#post = Post.new(params[:post].merge!(:user_id => session[:user_id]))

Resources