I'm having issues trying to access my associated model in a one-to-many relationship during testing. I don't understand why the below doesn't return the association.
The reason I care about this problem is because I wanted to use Factory Girl to call my guardian through my_factory_girl_user.guardians.first which wasn't working. I tried to simplify by using just ActiveRecord and it still isn't behaving how I would expect.
I'm a true Rails beginner so any advice on this problem or how to better debug it would be much appreciated!
Question: Why doesn't #target_user.guardians return anything?
User Model
class User < ActiveRecord::Base
has_many :guardians, dependent: :destroy
...
end
Guardian Model
class Guardian < ActiveRecord::Base
belongs_to :user
...
end
Controller Spec
describe 'PUT #update' do
context 'when logged in and authorized' do
let(:new_attributes){
{level: 31}
}
it 'sets current guardian' do
#target_user = User.create!(profile_name: 'MyGuardian', system: 'Xbox One', region: 'North America', password: "password", password_confirmation: "password")
#target_guardian = Guardian.create!(level: 31, guardian_class: 'Titan', activity: 'Vault of Glass', user_id: #target_user.id)
puts "User: #{#target_user.inspect}"
puts "Guardian: #{#target_guardian.inspect}"
puts "user.guardians: #{#target_user.guardians}"
put :update, {id: #target_guardian.id, guardian: new_attributes}, {user_id: #target_user.id}
expect(assigns(:guardian)).to eq(#target_guardian)
end
end
Output
User: #<User id: 3147, email: nil, password_digest: "$2a$04$QPk3kCsKLp0IX.YmMPAPdO2gCe79mwVybHkFpcEsVkG...", created_at: "2014-12-16 02:55:57", updated_at: "2014-12-16 02:55:57", is_admin: nil, profile_name: "MyGuardian", system: "Xbox One", region: "North America">
Guaridan: #<Guardian id: 3284, created_at: "2014-12-16 02:55:57", updated_at: "2014-12-16 02:55:57", user_id: 3147, fireteam_id: nil, level: 31, guardian_class: "Titan", activity: "Vault of Glass", comment: nil>
user.guardians:
Answer:
Thanks Typpex for the help! My issue was that I was not refreshing the User after creating the Guardian, the solution I used was adding the below line after creating my Guardian. For more information read the answer below.
#target_user.reload
It is because you never actually added the guardian to the user's guardian list such as:
#target_user.guardians << #target_guardian
Only after that you will be able to see guardians in #target_user.guardians.
When testing with rspec you need to call the action in your controller that is actualling adding the guardian to the guardians collection in user and then test if the guardian was correctly added with something like
#target_user.guardians.size.should_be 1
Related
I have two models, Event and Registration:
class Event < ApplicationRecord
has_many :registrations
end
class Registration < ApplicationRecord
belongs_to :event
end
I also have fixtures for these models:
event_one:
[...]
registration_one:
event: event_one
However, in test, if I get the event and check its registrations, there are none.
event = events(:event_one)
puts event.registrations.count # Prints: 0
How do I make sure these associations are loaded for tests?
I did a simple example of your code and try solve the problem, the first thinks I found when review the Event and Registration entries is that the Registrations created has event_ids that dont exist in your database.
A solution:
events.yml
event_one:
id: 111
name: MyString
description: MyString
registrations.yml
registration_one:
name: MyString
event_id: 111
registration_two:
name: MyString
event_id: 111
event_test.rb
require 'test_helper'
class EventTest < ActiveSupport::TestCase
def setup
#event = events(:event_one)
end
test "the truth" do
byebug
end
end
debugger:
(byebug) #event.registrations
#<ActiveRecord::Associations::CollectionProxy [#<Registration id: 357093202, name: "MyString", event_id: 111, created_at: "2021-01-09 17:21:48", updated_at: "2021-01-09 17:21:48">, #<Registration id: 1055835082, name: "MyString", event_id: 111, created_at: "2021-01-09 17:21:48", updated_at: "2021-01-09 17:21:48">]>
I don't think this is the better solution but work.
I have three models, related with has_many :through associations:
class Account < ApplicationRecord
has_many :account_owners
has_many :employees, through: account_owners
def is_owned_or_belongs_to_team_of_employees(employee)
employee.team.any? { |m| employees.include?(m) }
end
end
class AccountOwner < ApplicationRecord
belongs_to :account
belongs_to :employee
end
class Employee < ApplicationRecord
has_many :account_owners
has_many :accounts, through: :account_owners
def team
self.class.where(
'id IN (?)',
self. class.find_by_sql(['WITH RECURSIVE search_tree(id, path) AS (
SELECT id, ARRAY[id]
FROM employees
WHERE id = ?
UNION ALL
SELECT employees.id, path || employees.id
FROM search_tree
JOIN employees ON employees.manager_id = search_tree.id
WHERE NOT employees.id = ANY(path)
)
SELECT id FROM search_tree ORDER BY path',
self.id])
).order(:id)
end
end
I'm manually testing, in the Rails console in my development environment (using some fixtures that I first loaded on the database), the Account#is_owned_or_belongs_to_team_of_employees method.
When I run the method in the console this is what happens:
> a = Account.first
=> #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55">
> e = Employee.find_by(first_name: 'Elena')
=> #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>
> e.team
=> #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]>
> a.is_owned_or_belongs_to_team_of e
=> nil
> a.is_owned_or_belongs_to_team_of e
=> true
As you can see, the method returns nil (wrong!) the first time, and returns true (correct!) the following times.
The amazing thing is that I can correct the problem if I define the method like this:
def is_owned_or_belongs_to_team_of employee
puts "employees are #{employees.inspect}"
employee.team.any? { |m| employees.include?(m) }
end
Now the execution is correct, and the method returns consistently the same result (true in my example):
> a = Account.first
=> #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55">
> e = Employee.find_by(first_name: 'Elena')
=> #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>
> e.team
=> #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]>
> a.is_owned_or_belongs_to_team_of e
=> true
> a.is_owned_or_belongs_to_team_of e
=> true
If I remove the puts statement, we are back to square one: the method returns nil the first time, and true the following times.
And, amazingly, if I keep the puts statement but remove the inspect (that is, I just do puts "employees are #{employees}" we are also back to square one: nil the first time, and true the following times.
Any idea? What is going on here?
By the way, I'm running Ruby 2.5.1 y Rails 5.2.0.
I'm glad I stumbled upon this Unicorn of a bug!
After debugging this for hours, I found out the following:
any? had new changes in rails 5.2 release that was supposed to delegate it to Enumerable
the surprising thing, that if you put a binding.pry in the implementation of any? and call super it returns true even the first time and then the method returns nil. ~/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/relation.rb # line 228 ActiveRecord::Relation#any?:
if you add to employee.team .to_a everything works consistently.
if you put any? { |_| true } it returns true.
If you check for the value inside the block for include? it returns true but any? still returns nil!!!
If you avoid resolving the has_may through association (by calling .to_a before the block) or even using a different association inside the any? block everything works as expected.
using any other ruby version fixes the problem.
Summary
The problem was introduced in ruby 2.5.1 rails v5.2.0 when ActiveRecord::Relation started to include Enumerable.It happens with %w(none? any? one? many?) while trying to resolve a has many through association in its block.
This is driving me insane. I've stripped this down to the bare minimum without losing context (I think!)
All I am trying to do is check that when I update a value and save it to the database, that the value was saved. I want to do this because I need to write some other code that conditionally prevents this in the before_save callback, and I can't test that until I'm sure this is working!
The factories and the spec are below, I'm sure its something really stupid but I just can't figure it out...
FactoryGirl.define do
factory :programme do
name 'Trainee Programme'
end
factory :membership do
programme
end
factory :specialty do
sequence(:name) { |n| "Specialty #{n}" }
end
factory :user do
sequence(:email) { |n| "factorygirl-user-#{n}#remailer.org" }
password 'password'
password_confirmation 'password'
factory :trainee, class: User do
sequence(:email) { |n| "factorygirl-trainee-#{n}#remailer.org" }
name 'Factory Girl Trainee'
after(:create) do |user|
FactoryGirl.create(:membership, user: user, start_date: 1.day.ago)
end
end
end
end
describe Membership do
let(:trainee) { FactoryGirl.create(:trainee) }
it 'sets specialty' do
puts trainee.current_membership.inspect
trainee.current_membership.specialty = specialty
puts trainee.current_membership.inspect
trainee.current_membership.save!
puts trainee.current_membership.inspect
expect(trainee.current_membership.specialty).to eq(specialty)
end
end
The spec is failing because the expect sees a nil value. When I run the code the debug output I get is:
#<Membership id: 11, user_id: 11, programme_id: 11, start_date: "2015-03-10", end_date: nil, created_at: "2015-03-11 22:02:51", updated_at: "2015-03-11 22:02:51", options: {}, specialty_id: nil, membership_type_id: nil>
#<Membership id: 11, user_id: 11, programme_id: 11, start_date: "2015-03-10", end_date: nil, created_at: "2015-03-11 22:02:51", updated_at: "2015-03-11 22:02:51", options: {}, specialty_id: nil, membership_type_id: nil>
#<Membership id: 11, user_id: 11, programme_id: 11, start_date: "2015-03-10", end_date: nil, created_at: "2015-03-11 22:02:51", updated_at: "2015-03-11 22:02:51", options: {}, specialty_id: nil, membership_type_id: nil>
So its as if the assignment of specialty never happens??
Try reloading trainee, e.g.
expect(trainee.reload.current_membership.specialty).to eq(specialty)
Thanks to BroiState and Mori giving me some pointers I was able to establish that it was related to persistence (in particular one of my object methods not respecting it!)
The code for trainee.current_membership is as follows:
def current_membership
return unless memberships.current.any?
memberships.current.first
end
which uses these related scopes in Membership...
scope :started, -> { self.where("#{table_name}.#{_start_field}::TIMESTAMP < '#{Time.now}'") }
scope :not_ended, -> { self.where("#{table_name}.#{_end_field} IS NULL OR #{table_name}.#{_end_field}::TIMESTAMP > '#{Time.now}'") }
scope :current, -> { self.started.not_ended }
So each call to trainee.current_membership was giving me a new instance of the 'current' membership record
by explicitly using the same object the spec passed fine, i.e.:
it 'sets specialty' do
membership = trainee.current_membership
membership.specialty = specialty
membership.save!
expect(membership.specialty).to eq(specialty.reload)
end
The only reason I could think of is that speciality is a new, non-persisted record. Since membership belongs_to :speciality and membership is already persisted, it will not save associated object neither on assignment nor on save. In short, make sure that speciality is saved before creating association.
I am migrating an app from rails3.2.13 to rails4.0.0-rc1. I am having the following code:
class Foo < ActiveRecord::Base
has_many :bars
before_create :build_bars
private
def build_bars
self.bars.build({name: 'Bar 1'})
self.bars.build({name: 'Bar 2'})
end
end
The code above worked in rails3, but creates empty records in rails4. Some try & error in the console revealed that, indeed, attributes are not assigned.
f = Foo.new
f.bars.build({name: 'Bar'})
=> #<Bar id: nil, name: nil>
What's the proper way to build associations and have them being saved together with its parent record?
i think that #Mischa is right. i've been migrating my app over to rails4 and it works:
user.authorizations.build provider: "bla"
=> #<Authorization id: nil, provider: "bla", uid: nil, user_id: 1, created_at: nil, updated_at: nil>
you can have a look at the changes i did: https://github.com/phoet/on_ruby/pull/83/files#L23L59
most probably it's removing:
# Mass assignment settings
config.active_record.whitelist_attributes = true
Let's say I have a devise model called "User" which has_many :notes and :notebooks and each :notebook has_many :notes.
So a notes will have two foreign key, :user_id and :notebook_id, so how to build/find a note?
current_user.notebooks.find(param).notes.new(params[:item]) will create the foreign_key only for notebook or also for the user in the note record in the DB?
If the second case (foreign key only for notebook), how should I write?
Using MongoDB with MongoID and referenced relations
Mongoid will manage the document references and queries for you, just make sure to specify the association/relationship for each direction that you need (e.g., User has_many :notes AND Note belongs_to :user). Like ActiveRecord, it appears to be "smart" about the relations. Please do not manipulate the references manually, but instead let your ODM (Mongoid) work for you. As you run your tests (or use the rails console), you can tail -f log/test.log (or log/development.log) to see what MongoDB operations are being done by Mongoid for you and you can see the actual object references as the documents are updated. You can see how a relationship makes use of a particular object reference, and if you pay attention to this, the link optimization should become clearer.
The following models and test work for me. Details on the setup are available on request. Hope that this helps.
Models
class User
include Mongoid::Document
field :name
has_many :notebooks
has_many :notes
end
class Note
include Mongoid::Document
field :text
belongs_to :user
belongs_to :notebook
end
class Notebook
include Mongoid::Document
belongs_to :user
has_many :notes
end
Test
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
User.delete_all
Note.delete_all
Notebook.delete_all
end
test "user" do
user = User.create!(name: 'Charles Dickens')
note = Note.create!(text: 'It was the best of times')
notebook = Notebook.create!(title: 'Revolutionary France')
user.notes << note
assert_equal(1, user.notes.count)
user.notebooks << notebook
assert_equal(1, user.notebooks.count)
notebook.notes << note
assert_equal(1, notebook.notes.count)
puts "user notes: " + user.notes.inspect
puts "user notebooks: " + user.notebooks.inspect
puts "user notebooks notes: " + user.notebooks.collect{|notebook|notebook.notes}.inspect
puts "note user: " + note.user.inspect
puts "note notebook: " + note.notebook.inspect
puts "notebook user: " + notebook.user.inspect
end
end
Result
Run options: --name=test_user
# Running tests:
user notes: [#<Note _id: 4fa430937f11ba65ce000002, _type: nil, text: "It was the best of times", user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), notebook_id: BSON::ObjectId('4fa430937f11ba65ce000003')>]
user notebooks: [#<Notebook _id: 4fa430937f11ba65ce000003, _type: nil, user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), title: "Revolutionary France">]
user notebooks notes: [[#<Note _id: 4fa430937f11ba65ce000002, _type: nil, text: "It was the best of times", user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), notebook_id: BSON::ObjectId('4fa430937f11ba65ce000003')>]]
note user: #<User _id: 4fa430937f11ba65ce000001, _type: nil, name: "Charles Dickens">
note notebook: #<Notebook _id: 4fa430937f11ba65ce000003, _type: nil, user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), title: "Revolutionary France">
notebook user: #<User _id: 4fa430937f11ba65ce000001, _type: nil, name: "Charles Dickens">
.
Finished tests in 0.018622s, 53.6999 tests/s, 161.0998 assertions/s.
1 tests, 3 assertions, 0 failures, 0 errors, 0 skips
I would use
class User
has_many :notebooks
has_many :notes, :through => :notebooks
end
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Update
You could always set the user_id manually, like this (I assume param is the ID for your notebook?):
Notebook.find(param).notes.new(params[:item].merge(:user_id => current_user.id))