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.
Related
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.
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
previous rails 4 I had in a model
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
...
end
But now strong_parameters replaced the protected_attributes so I comment it and use permit.
Now I discovered that I can access attribute without permitting it.
In rails c I manage to do this:
2.0.0p247 :002 > User.new(admin: "1")
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: true>
2.0.0p247 :016 > user = User.new(name: 'Nir', email: 'nir#example.com', password: 'foobar', password_confirmation: 'foobar', admin: "1")
=> #<User id: nil, name: "Nir", email: "nir#example.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$xVnY8ydd5SoaLVipK5j4Del40FrOmu4bKypGjBEwvms7...", remember_token: nil, admin: true>
When obviously I should not be able to set and change the admin attribute. Only user.toggle(:admin) should be able to.
So what am I not understanding or should do right.
And how to make this test pass:
describe "accessible attributes" do
it "should not have allow access to admin" do
expect do
User.new(admin: "1")
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
To prevent a user from setting the admin property, you should not add it as a parameter of the permit method.
params.require(:user).permit(:name, :whatever_else_you_allow)
Keywords in this are: params (it deals with parameters) and permit (you tell rails which properties to allow for).
Strong Parameters will make Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted. In your test however, you set the property directly on the model. Nothing prevents you from doing that.
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
This is really stumping me. The process works fine if I go about it with #new and then #save, but #create returns a model instance with all the fields set to nil.
e.g:
Unexpected behavior:
ruby-1.9.2-p0 > EmailDefault.create(:description=>"hi")
=> #<EmailDefault id: nil, description: nil, created_at: nil, updated_at: nil>
Expected behaviour:
ruby-1.9.2-p0 > e = EmailDefault.new
=> #<EmailDefault id: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p0 > e.description = "hi"
=> "hi"
ruby-1.9.2-p0 > e.save
=> true
ruby-1.9.2-p0 > EmailDefault.last
=> #<EmailDefault id: 4, description: "hi", created_at: "2011-02-27 22:25:33", updated_at: "2011-02-27 22:25:33">
What am I doing wrong?
--update--
Turns out I was mis-using attr_accessor. I wanted to add some non-database attributes, so I did it with:
attr_accessible :example_to, :cc_comments
which is wrong, and caused the situation #Heikki mentioned. What I need to do is:
attr_accessor :example_to, :cc_comments
You need to white list those properties with attr_accessible to enable mass-assignment.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_accessible
--edit
By default all attributes are available for mass-assignment. If attr_accessible is used then mass-assignment will work only for those attributes. Attr_protected works the opposite way ie. those attributes will be protected from mass-assignment. Only one should be used at a time. I prefer the white listing with attr_accessible.