Padrino + FactoryGirl not performing Lazy Loading - ruby

I'm developing my thesis on Padrino, using Active Record as ORM and FactoryGirl as Mocking framework.
I'm facing a strange behavior.
I've two models: User and Rate.
- User has a 'has_many :rates' association;
- Rate has a 'belongs_to :user' association;
- 'rates' table has an integer attribute named 'user_id' (not created with 'references' on migration, but directly with 'integer').
My association is working well, but only after performing a reload on parent object.
Here are the snippets related to this issue:
https://bitbucket.org/snippets/cuscos/MbdAK
If I start a 'Padrino Console' and create a user manually, this is the current behavior:
$ user = FactoryGirl.create(:user_with_rates)
$ user.rates.length # Received '0', but expected '1'
$ user.rates.all.length # Received '1', OK
$ user.reload!
$ user.rates.length # Now I'm receiving '1' correctly
It seems that ActiveRecord isn't performing the Lazy Loading for any reason.
Does anyone know why is this happening?
Thanks for all support so far.

For those who it may interest, here is the solution I'm adopting to solve this problem:
In User factory, instead:
after(:create) do |user, evaluator|
create_list(:rate, evaluator.rates_count, user: user)
end
Do:
after(:create) do |user, evaluator|
user.rates << create_list(:rate, evaluator.rates_count, user: user)
end
It's not a proper solution, but solved my problem for now.
Cheers o/

Related

Ruby on Rails ActiveRecord filter issue [duplicate]

I am working on an app that allows Members to take a survey (Member has a one to many relationship with Response). Response holds the member_id, question_id, and their answer.
The survey is submitted all or nothing, so if there are any records in the Response table for that Member they have completed the survey.
My question is, how do I re-write the query below so that it actually works? In SQL this would be a prime candidate for the EXISTS keyword.
def surveys_completed
members.where(responses: !nil ).count
end
You can use includes and then test if the related response(s) exists like this:
def surveys_completed
members.includes(:responses).where('responses.id IS NOT NULL')
end
Here is an alternative, with joins:
def surveys_completed
members.joins(:responses)
end
The solution using Rails 4:
def surveys_completed
members.includes(:responses).where.not(responses: { id: nil })
end
Alternative solution using activerecord_where_assoc:
This gem does exactly what is asked here: use EXISTS to to do a condition.
It works with Rails 4.1 to the most recent.
members.where_assoc_exists(:responses)
It can also do much more!
Similar questions:
How to query a model based on attribute of another model which belongs to the first model?
association named not found perhaps misspelled issue in rails association
Rails 3, has_one / has_many with lambda condition
Rails 4 scope to find parents with no children
Join multiple tables with active records
You can use SQL EXISTS keyword in elegant Rails-ish manner using Where Exists gem:
members.where_exists(:responses).count
Of course you can use raw SQL as well:
members.where("EXISTS" \
"(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
count
You can also use a subquery:
members.where(id: Response.select(:member_id))
In comparison to something with includes it will not load the associated models (which is a performance benefit if you do not need them).
If you are on Rails 5 and above you should use left_joins. Otherwise a manual "LEFT OUTER JOINS" will also work. This is more performant than using includes mentioned in https://stackoverflow.com/a/18234998/3788753. includes will attempt to load the related objects into memory, whereas left_joins will build a "LEFT OUTER JOINS" query.
def surveys_completed
members.left_joins(:responses).where.not(responses: { id: nil })
end
Even if there are no related records (like the query above where you are finding by nil) includes still uses more memory. In my testing I found includes uses ~33x more memory on Rails 5.2.1. On Rails 4.2.x it was ~44x more memory compared to doing the joins manually.
See this gist for the test:
https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1
where.missing (Rails 6.1+)
Rails 6.1 introduces a new way to check for the absence of an association - where.missing.
Please, have a look at the following code snippet:
# Before:
Post.left_joins(:author).where(authors: { id: nil })
# After:
Post.where.missing(:author)
And this is an example of SQL query that is used under the hood:
Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL
As a result, your particular case can be rewritten as follows:
def surveys_completed
members.where.missing(:response).count
end
Thanks.
Sources:
where.missing official docs.
Pull request.
Article from the Saeloun blog.
Notes:
where.associated - a counterpart for checking for the presence of an association is also available starting from Rails 7.
See offical docs and this answer.

Method missing in User model, Rails 4.1

This should be easy but I'm obviously missing something. Working through a testing exercise, and attempting to create a method on the User model. Seems like it should be incredibly straightforward, but Rails is throwing method missing whenever I attempt to access the method.
Here's the code for the file app/models/user.rb:
class User < ActiveRecord::Base
has_secure_password
validates :email, presence: true, uniqueness: true
def administrator?
end
end
At this point I'm working through TDD process, and only attempting to satisfy the test exception by creating the method (I'll add code for the administrator? method later). Here's the line from the spec:
expect(admin.administrator?).to be_true
And the output from the test:
1) User management adds a new user
Failure/Error: expect(admin.administrator?).to be_true
expected to respond to `true?`
However, I can't seem to access the freaking method! Whenever I call the #administrator? method on an object of the User class I get:
NoMethodError: undefined method `administrator?' for #<User:0x007f9ac6929358>
Here's the pry response to 'ls -M' when I'm in the User class, showing the methods available:
User#methods: administrator password password_confirmation
Here's the output from a pry console session:
[73] pry(User):1> admin = User.first
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1, email: "test#yahoo.com", password_digest: "secret123", created_at: "2015-06-25 17:33:25", updated_at: "2015-06-25 17:33:25", admin: true, firstname: nil, lastname: nil>
[74] pry(User):1> admin.valid?
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" = 'test#yahoo.com' AND "users"."id" != 1) LIMIT 1
=> true
[75] pry(User):1> admin.instance_of?(User)
=> true
[76] pry(User):1> admin.administrator?
NoMethodError: undefined method `administrator?' for #<User:0x007f9ac6929358>
from /Users/me/.rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/activemodel-4.1.1/lib/active_model/attribute_methods.rb:435:in `method_missing'
I've tried defining the method as self.administrator as well, with the same results. I went back and looked at the model methods defined in the Rails Tutorial, and I can't see anything about Hartl's model that helps me understand why my method isn't working.
Any help would be appreciated! I'm missing something basic here it seems...
**** EDIT/UPDATE ****
After getting a response about how to return the boolean :admin column from the user object I realized that it's worth clarifying the real question. The real issue I was trying to understand isn't so much whether the administrator? method is the best way to determine whether a given user is an admin or not (as #sajinmp very helpfully points out below, the object will respond to admin? because of the corresponding column in the database).
My real question/frustration was with why I couldn't get the object to respond to that or any other methods created in the model.
If all you are doing in the method is to check whether a user is an admin or not you do not need such a specific method. Since in your user model the administrator is represented by admin simply calling admin? is enough.
current_user.admin (<admin.admin> in this case)
will answer your call. But when admin is nil this will be a problem so it is better to use
admin.try(:admin)
This will not throw an error even if admin is nil.
I restarted the Rails server, and that seemed to fix it. After cycling the server and starting a new console session, the user object now responds to the methods in user.rb.
Should have done that sooner (DUH!), but I'd assumed that it wouldn't be necessary. Within the console session I could use reload! and then the Pry show-source method would confirm that the methods were appearing in the console as written. I didn't think that the console relied on the server, but I must be wrong..?
Appreciate any confirmation on this.

Rails 4 update Type when migrating to Single Table Inheritance

Rails 4.0.4, Ruby 2.1.2
I want to use STI like so:
User < ActiveRecord::Base
Admin < User
But currently I have:
User < ActiveRecord::Base
Info < ActiveRecord::Base
So I changed my models, and then start writing my migration. In my migration, I first add a column to allow STI:
add_column :users, :type, :string
Then I want to update the Users currently in the database to be Admin
# Place I'm currently stuck
Then I move all my Info records into the Users table
Info.all.each { |info| User.create(name: info.name, email: info.email) }
Everything seems to work except turning the previous Users into Admins. Here are some things I've tried:
# Seems to work, but doesn't actually save type value
User.each do |user|
user.becomes!(Admin)
user.save! # evaluates to true, doesn't have any errors
end
# Seems to work, but doesn't actually save type value
# I've also tried a combo of this one and the above one
User.each do |user|
user.type = "Admin"
user.save! # evaluates to true, doesn't have any errors
end
User.each do |user|
user = user.becomes!(Admin)
user.save! # evaluates to true, doesn't have any errors
end
# Seems to work, but doesn't actually save type value
User.each do |user|
user.update_attributes(type: "Admin")
end
Each time the local user variables seems to have the correct type ("Admin"), along with save evaluating to true, but when I check Admin.count or check Users type value, it is always nil. I know you're not supposed to change them, but this is just to migrate the data over to STI and then I'll be able to start creating Users or Admin with the proper class.
At the very least I think Rails should raise an error, set an error or somehow let the developer know it's failing the save calls.
It turns out that while update_attributes doesn't work for type (I haven't researched why yet), update_column does work.
So the migration simply becomes:
User.each do |user|
user.update_columns(type: "Admin")
end
The reason this works and other updates don't can probably be traced back to either callbacks or validations not being run. I have no callbacks that would prevent it, but maybe there are default Rails ones for type
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
If you had more rows in the database User.each would become quite slow as it makes an SQL call for each user.
Generally you could use User.update_all(field: value) to do this in one SQL call but there is another reason to avoid this: if the User model is later removed the migration will no longer run.
One way to update all rows at once without referencing the model is to use raw SQL in the migration:
def up
execute "UPDATE users SET type = 'Admin' WHERE type IS NULL"
end

Does adding a new object in a has_one relationship, not update the association?

I have 2 models, an example:
class Report ...
belongs_to :answer_sheet
end
class AnswerSheet ...
has_one :report
end
When I do a:
#answersheet.report = Report.create(:data => 'bleah')
#answersheet.save
# and then create another report and assign it to the same #answersheet
# assuming at this stage #answersheet is already reloaded
#answersheet.report = Report.create(:data => 'new data')
#answersheet.save
# (irb) #answersheet.report returns the first report with the data 'bleah' and not
# the one with the new data.
Is this supposed to be the correct behavior?
If I want to update the association to the later report, how should I go about doing it?
It took me a few tries to see what you were talking about. But I got it now.
Take a look at the SQL and you'll find ActiveRecord is doing a select and then adding ASC and LIMIT 1. There can be more than one report records that refer to the same answer_sheet.
You can prevent this situation by adding a validation that checks for uniqueness of answer_sheet_id.
You should also start using save! and create! (note the bang operators) so exceptions are thrown during validation.
Lastly, calling Report.create followed by #answersheet.save performs two database transactions, whereas Report.new followed by #answersheet.save would perform just one.

Datamapper: report why I can't destroy record

I'm setting up my db model using datamapper and dm-contraints. I have two models which have a many to many relationship but when I try to destroy one, the only message I get is false.
Is it possible to get datamapper to give me more feedback one which relationship is exactly causing the problem?
With datamapper 1.2.1:
def why_you_no_destroy? model
preventing = []
model.send(:relationships).each do |relationship|
next unless relationship.respond_to?(:enforce_destroy_constraint)
preventing << relationship.name unless relationship.enforce_destroy_constraint(model)
end
preventing
end
Unfortunately DM doesn't provide a way to report why destroy failed.
Most of time the destroy failed because of its associations. DM have a mechanism to avoid orphan records.
To avoid this kind of destroy failed, you can Use dm-constraints(https://github.com/datamapper/dm-constraints ) to set up true database level foreign key references, which default to protect, but can be set to cascade deletes instead.
class List
has n, :todos, :constraint => :destroy (or :destroy!)
end
Sadly, Currently dm-constraints only supports PostgreSQL and MySQL.
For other database, you can check all the associations manually and delete them first, then delete the model。
You can get information on DataMapper errors from
model.destroy
if model
model.errors.each do |error|
p error
end
end
Sometimes that doesn't tell you anything though, in which case you can put your code inside of a begin/rescue block e.g.
begin
model.destroy
rescue Exception => exc
p exc
end

Resources