Background
I'm attempting to test my models.
app/models/user.rb
class User < ActiveRecord::Base
has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id
def transactions
transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
transactions
end
end
app/models/transaction.rb
class Transaction < ActiveRecord::Base
attr_accessor :user
belongs_to :payor, class_name: 'User'
belongs_to :payee, class_name: 'User'
end
In the Transactions class, #user is an ephemeral object instance representing the user accessing the model.
spec/models/user_spec.rb
require 'spec_helper'
describe User do
let(:user) { Factory(:user) }
let(:user2) { Factory(:user) }
let(:user3) { Factory(:user) }
let(:transaction_user_user2) { Factory(:transaction, payor: user, payee: user2) }
let(:transaction_user2_user) { Factory(:transaction, payor: user2, payee: user) }
let(:transaction_user2_user3) { Factory(:transaction, payor: user2, payee: user3) }
describe ".transactions" do
it "should include payor and payee transactions but not 3rd party transactions" do
user.transactions.should == [transaction_user_user2, transaction_user2_user]
user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
user3.transactions.should == [transaction_user2_user3]
end
end
end
Using rspec 2.6.4, factory_girl 2.1.2, rails 3.1.0, ruby 1.9.2p290. As shown, the spec passes.
Problem
When I modify the transactions method in app/models/user.rb to iterate over the results such that it reads:
class User < ActiveRecord::Base
has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id
def transactions
transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
transactions.each {|transaction| transaction.user = self}
transactions
end
end
the method transactions now returns [] in rspec, however it works perfectly in the app views.
Since Transaction.user is ephemeral (representing the user accessing the transaction) it must be set (if it exists) every time a Transaction is initialized or built from db records.
I'm at a loss for where to begin to debug this.
All suggestions appreciated!
I think your problem lies in the fact that let is lazy. Basically what is happening is that the transactions are not even created yet when the transactions method is called in the test. Use let! for a non-lazy version. See let and let! for more details.
Couldn't you just return payor_transactions + payee_transactions instead of manually selecting them?
Following the suggestion from #obrok, the solution I settled on to retain the advantage of lazy-loading let in other tests was to touch each transaction before testing User#transactions as so:
describe ".transactions" do
it "should include payor and payee transactions but not 3rd party transactions" do
[transaction_user_user2, transaction_user2_user, transaction_user2_user3].each do |transaction|
[transaction.payor_id, transaction.payee_id].each {|id| id.should_not be_nil }
end
user.transactions.should == [transaction_user_user2, transaction_user2_user]
user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
user3.transactions.should == [transaction_user2_user3]
end
end
Related
I'm having a tough time figuring something out in Rails. It probably has to do with my very limited knowledge of SQL, since I know Rails pretty well. I'm using Rails 5.
I have two models: Applicant and Application.
class Applicant < ApplicationRecord
has_one :application
has_many :skills
accepts_nested_attributes_for :application
accepts_nested_attributes_for :skills,
reject_if: ->(skill) { skill[:name].empty? || skill[:experience].empty? }
validates_with ApplicantValidator
end
class Application < ApplicationRecord
belongs_to :applicant
has_many :notes
VALID_STATUSES = ["in review", "accepted", "declined", "closed"]
validates_length_of :why_interested, minimum: 25
validates :accept_terms, acceptance: true
validates :status, inclusion: { in: VALID_STATUSES }
before_validation :set_status
private
def set_status
self.status ||= "in review"
end
end
I'd like to add a scope, :active, to the Applicant model that returns only applicants who have an application whose status is "in review". However, I can't find a way to access the application within a scope proc.
I've seen other suggestions for cases where there is a has_many relationship with the child, but they didn't work in my case.
I doubt it makes a difference, but I'm using Postgres. The closest I've come to a solution is to add this, but when I run RSpec it says there needs to be a FROM-clause for the applications table. I don't know how to effect that.
scope :active, -> { joins(:application).where('"application"."status" = "in review"') }
scope :in_review_applicants, -> { joins(:application).where('application.status = ?', :in_review) }
I think is something like that..
I have a many to many relationship between two models as follows:
#users.rb
has_many :users_to_roles
has_many :roles, through: :users_to_roles
#users_to_roles.rb
belongs_to :user
belongs_to :role
#roles.rb
has_many :users_to_roles
has_many :users, through: :users_to_roles
I want to disable the deletion of roles if there are users who are "in this role". Here I have found two options who should do the work:
:restrict_with_exception causes an exception to be raised if there are
any associated records :restrict_with_error causes an error to be
added to the owner if there are any associated objects
but there is no example with the syntax of this and how it should work.
Could you help to make this valid:
#roles.rb
has_many :users_to_roles
has_many :users, through: :users_to_roles, dependent: restrict_with_exception
Such operations can be easily do using Callbacks. In my case, I have added the following method in my model:
# callbacks
before_destroy :check_for_users_in_this_role
def check_for_users_in_this_role
status = true
if self.security_users.count > 0
self.errors[:deletion_status] = 'Cannot delete security role with active users in it.'
status = false
else
self.errors[:deletion_status] = 'OK.'
end
status
end
Alternatively, you can rescue the exception in your controller. In this example, a contact may own interest, i.e.
class Interest < ActiveRecord::Base
belongs_to :contact
end
class Contact < ActiveRecord::Base
has_many :interests, :dependent => :restrict
end
Then in the controller:
def destroy
#contact = Contact.find(params[:id])
begin
#contact.destroy
rescue
flash[:msg] = "Can't delete - owns interest"
end
respond_to do |format|
format.html { redirect_to(:back) }
format.xml { head :ok }
end
end
The flash message will be displayed in the calling page.
The correct rails way is to do the following:
users.rb:
has_many :users_to_roles, dependant: :destroy # don't keep the join table entry if the user is gone
has_many :roles, through: :users_to_roles
Make sure that your join does not have redundant entries (in which either column is null or orphaned).
users_to_roles.rb:
belongs_to :user
belongs_to :role
# add validations presence of both user and role
# in both model and database.
Bonus, from rails 4.2 you can add forigen_key: true in your migration for referential integrity
Now in your role (I am assuming you name your models singularly and made a typo in the question), you add this:
role.rb:
has_many :users_to_roles, dependant: :restrict_with_error
has_many :users, through: :users_to_roles
I made it with my classes like this:
app/models/guest_chat_token.rb
class GuestChatToken < ApplicationRecord
has_many :chat_messages, as: :sendable, dependent: :restrict_with_exception
end
app/controllers/admin/application_controller.rb
class Admin::ApplicationController < ApplicationController
....
rescue_from ActiveRecord::DeleteRestrictionError do |exception|
redirect_to :back, notice:
"Be aware: #{exception.message}."
end
end
lets say I have two models
class User < ActiveRecord::Base
has_many :friendships, :dependent => :destroy
has_many :followings, :through => :friendships, :foreign_key => "followed_id"
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :following, :class_name => "User", :foreign_key => "followed_id"
end
now in my user_spec.rb I have this test
it "should delete all friendships after user gets destroyed" do
#user.destroy
[#friendship].each do |friendship|
lambda do
Friendship.find(friendship)
end.should raise_error(ActiveRecord::RecordNotFound)
end
end
is this the right place to test the :dependent => :destroy relation or does this belong inside the friendship_spec.rb or doesn't it matter in which of the two specs I test this?
You might consider using shoulda_matchers to test your associations:
# user_spec.rb
it { should have_many(:friendships).dependent(:destroy) }
# friendship_spec.rb
it { should belong_to(:user) }
Having each model test its own associations is the best approach IMHO.
This sort of thing is sometimes a matter of taste, but I think the spec for User is probably the best place to test this. The method you're calling to start the test is a method on User, so it makes sense to test it along the other tests for User as well.
I recently upgraded an app to rails 3.2.2.
I'm using Factory_girl
Factory.sequence :name do |n| "name-#{n}" end
Factory.define :user do |u| u.first_name{ Factory.next(:name) }
u.last_name { |u| 'last_' + u.first_name } u.password 'secret'
u.password_confirmation { |u| u.password } u.sequence(:email) { |i|
"user_#{i}#example.com" }
end
and this simple test
specify { Factory.build(:user).should be_valid }
generate the following warning
DEPRECATION WARNING: You're trying to create an attribute user_id'.
Writing arbitrary attributes on a model is deprecated. Please just use
attr_writer` etc. (called from block (2 levels) in
at...
How can I get rid of it?
It's probably because you haven't prepared/migrated your test database with updated column definitions, thus it thinks you're trying to arbitrarily set the attribute.
Run rake db:test:prepare to make sure it's updated.
Here's the source code of that method, where you can see Rails checks for the column or attribute first, then warns if they're not found.
I've met the same warning with the following code:
Ad model:
class Ad < ActiveRecord::Base
belongs_to :user
end
Factories:
FactoryGirl.define do
factory :ad do
association :user
end
end
FactoryGirl.define do
factory :user do
first_name {Factory.next(:first_name)}
last_name {Factory.next(:last_name)}
email {|x| "#{x.first_name}.#{x.last_name}#{Factory.next(:count)}#test.com"}
password Forgery(:basic).password
confirmed_at Date.today << 10
end
end
Test
require 'spec_helper'
describe Ad do
before(:each) do
#ad = Factory.build(:ad)
end
"it is not valid without a user"
end
Running the test gave me a similar error.
Adding
attr_accessor :user
to the Ad model fixed the warning.
I hope it helps.
I had this same warning while doing tests in Rspec and my issue was that I had a Parent model and Child model where I accidentally had this:
class Child < ActiveRecord::Base
belongs_to :parent
end
......
class Parent < ActiveRecord::Base
belongs_to :child
end
What I have now:
class User < ActiveRecord::Base
has_many :people
end
... and...
class Person < ActiveRecord::Base
belongs_to :user
end
In spec/factories.rb:
Factory.define :user do |u|
u.email "test#test.com"
u.password "testpassword"
u.password_confirmation "testpassword"
u.display_name "neezer"
# u.people { |i| [i.association(:person)] }
end
Factory.define :person do |p|
p.first_name "p_firstname"
p.last_name "p_lastname"
p.gender "male"
p.association :user
end
I want to setup the user factory to create with 1 person association, but if I uncomment that line, when I run my tests, my system hangs for quite some time, before outputting this failure:
1) User can be created from a factory
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
# /Users/test/.rvm/gems/ruby-1.9.2-p0/gems/activerecord-3.0.5/lib/active_record/persistence.rb:285
What am I doing wrong here? I would like to have tests that require an association between these two models, such that (1) a User must have at least 1 person, and (2) a Person must belong to a User.
Is this a first-priority issue? I'll admit I'm a bit lost here...
I'm using rspec 2.5.0, factory_girl_rails 1.0.1, and rails 3.0.5.
My specs:
user_spec.rb:
require 'spec_helper'
describe User do
subject { Factory :user }
# ...
context "has associations, " do
it "can have people" do
subject.should respond_to :people
end
it "must have at least 1 person" do
subject.send "people=", nil
subject.should_not be_valid
subject.errors[:people].should_not be_empty
end
end
end
person_spec.rb:
require 'spec_helper'
describe Person do
subject { Factory :person }
# ...
context "has validation, " do
[:gender, :user].each do |attr|
it "must have a #{ attr }" do
subject.send "#{attr}=", nil
subject.should_not be_valid
subject.errors[attr].should_not be_empty
end
end
end
context "has associations, " do
it "can have a User" do
subject.should respond_to :user
end
end
end
Keep that line but remove p.association :user from your person factory.
I've since discovered Shoulda, which provides a nice rspec matchers like these:
subject.should belong_to :user
subject.should have_many :people
Which has solved my issue.