I trying to get mongoid to save associations, but I can only get one side to work. If I have the following test.
test "should add a user as a follower when a user follows the group" do
#cali_group.followers = []
#user1.followed_groups << #cali_group
assert_equal 1, #user1.followed_groups.count
assert_equal 1, #cali_group.followers.count
end
Which is failing, because #cali_group.followers is []. I've been working with this for awhile, tried #cali_group.reload. But it looks like the only way to do this in my code is to work both ends of the join, i.e. #cali_group.followers << #user1. I can do that in my code if I have to.
The models for polco_group and user are here: https://gist.github.com/1195048
Full test code is here: https://gist.github.com/1195052
It can be that:
https://github.com/mongoid/mongoid/issues/1204
Very late to the show. Using Mongoid 4.0.2 here. The issue is troubling me as well.
The link by #sandrew is no longer valid. A similar issue was reported here: http://github.com/mongodb/mongoid/pull/3604
The workaround that I found was:
#cali_group.followers = []
#cali_group.follower_ids # Adding this line somehow does something to the cache
#user1.followed_groups << #cali_group
Found this workaround by adding a before_save in the Group class and observing self.changes. Without this line, the follower_ids member changes from nil to []. However after adding the line, the correct ID of the user is received and set. Hope that helps any future reader.
Related
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.
Probably a stupid question but I was following along this article and came across a bit of code I couldn't quite grasp. Here it is:
class CreateArticle
attr_reader :validate_article, :persist_article
def initialize(validate_article, persist_article)
#validate_article = validate_article
#persist_article = persist_article
end
def call(params)
result = validate_article.call(params)
if result.success?
persist_article.call(params)
end
end
end
More specifically, the problematic line is this:
if result.success?
Here's my problem with it: where did the success? method come from? It's not default in Ruby, and result is a local variable, so it should be nearby. But even if it's just omitted in the code sample, where would it have to be defined for that line to work? Everywhere I tried to define it just gave me an 'undefined method' error.
For example, I tried to define it both in the CreateArticle class and in the (only alluded to) ValidateArticle class, the obvious culprits, but no dice.
Update:
The reason I ask is not so much about what success? does as it is because I'm interested in using the pattern in my code. So, for example, my version of the success? method could be just checking whether a value got updated, or an item was inserted into an array. For example, let's say it's just this:
def success? # or self.success?
return true
end
Problem is, I can find no place where I can put this that works. I even created a module just for it and included it into the class, and still it doesn't work (it just returns 'undefined method'). So I'm still at a loss as to where I would have to define such a method so that it would work the way it looks like it should.
It's a method that comes with rails. It checks.for a server response with a 200 code. If it gets a 200 code it returns true else it returns false. Read the rails API docs about it... https://apidock.com/rails/v3.2.3/ActiveResource/Response/success%3F
Actually . success? is a built in ruby method. Check here. What it actually does is checking Stat and returns a boolean.
I did some more digging around the blog and from what I found I suspect that the code is probably making use of the dry-monads gem:
You can explicitly check the type by calling failure? or success? on a monadic value.
It's not explicit in the code excerpt but it's the only thing that makes sense.
I am trying to read in Rspec 3.1 a cookie received after get call.
I see it is returned but the last_response.cookies doesn't exist.
How can I read response's cookie?
it "doesn't signs in" do
get '/ui/pages/Home'
puts last_response.cookies
end
I know it has been a while, but facing exactly this same issue now, after some struggle, I've found an article here that shares an interesting approach. As I also couldn't find any native parsed method for this, that has worked fine for me.
Basically, place this piece of code below on your spec/spec_helper.rb:
def cookies_from_response(response=last_response)
Hash[response["Set-Cookie"].lines.map { |line|
cookie = Rack::Test::Cookie.new(line.chomp)
[cookie.name, cookie]
}]
end
and you could use this to see the parsed hash:
puts cookies_from_response
For a cookie's value check, you could then use something like:
# Given your cookie name is 'foo' and the content is 'bar'
expect(cookies['foo'].value).to eq 'bar'
Hopefully this becomes helpful to others facing similar issues.
I'll be brief with the code samples, as all of my tests pass except the one below. I got it to pass by changing things up a bit, but I'm not sure why version 1 fails and version 2 works.
My model:
# app/models/person.rb
class Person
validates :contact_number, uniqueness: true
end
Model spec
# spec/models/person_spec.rb
require 'spec_helper'
describe Person do
it 'is a valid factory' do
create(:person).should be_valid # passes
end
it 'has a unique phone number' do
create(:person)
build(:person).should_not be_valid # fails
end
it 'also has a unique phone number' do
person1 = create(:person)
person2 = person1.dup
person2.should_not be_valid # passes
end
end
As far as I can tell, the two uniqueness tests should be doing the same thing, however one passes and one fails.
If it matters, I am using mongoid, though I don't think that should have any effect. I'm also not doing anything with nested contexts or describes in my test, so I think the scope is correct. Any insight is appreciated.
UPDATE 1: I realized in my factory I am adding an initialize_with block like this:
initialize_with { Person.find_or_create_by(contact_number: contact_number) }
I realized that this may be the reason the validation was failing -- I was just getting the same person back. However, commenting out that line gives the following error:
Mongoid::Errors::Validations:
Problem:
Validation of Person failed.
Summary:
The following errors were found: Contact number is already taken
Resolution:
Try persisting the document with valid data or remove the validations.
Which, in theory is good, I suppose, since it won't let me save a second person with the same contact number, but I'd prefer my test to pass.
Probably your person factory has a sequence in contact_number making a diferent contact_number in each person.
Just realize that the build(:person) doesnt validate. The validation occurs only in create.
I strongly suggest use of shoulda-matchers for this kind of validations.
It is possible that your database is being cleaned (do you have database-cleaner in your Gemfile), or your tests are not being run in the order you think they are. (Check for :random in your spec_helper.rb)
While the above answer regarding using shoulda-matchers will help you run this particular test in RSpec more concisely, you probably want to have your unique phone number test be able to be run completely on its own without relying on another spec having executed. Your second test is an example of Obscure Test (and also a little bit of Mystery Guest http://robots.thoughtbot.com/mystery-guest), where it's not clear from the test code what is actually being tested. Your phone number parameter is defined in another file (the factory), and the prior data setup is being run in another spec somewhere else in the file.
Your second test is already better because it is more explicitly showing what you're testing, and doesn't rely on another spec having been run. I would actually write it like this to make it more explicit:
it 'has a unique phone number' do
person1 = create(:person, phone_number: '555-123-4567')
person2 = create(:person, phone_number: '555-123-4567')
# can use 'should' here instead
expect(person2).not_to be_valid
end
If you don't explicitly make it about the phone number, then if you change your factory this test might start failing even though your code is still sound. In addition, if you have other attributes for which you are validating uniqueness, your previous test might pass even though the phone number validation is missing.
I figured it out! On a whim, I checked out the test database and noticed that a Person object was lingering around. So, it actually wasn't the
build(:person).should_not be_valid that was raising the Mongoid exception. It was the create call on the line before. Clearing out the DB and running the spec again passed, but again the data was persisting. I double checked my spec_helper.rb file and noticed I wasn't calling start on the DatabaseCleaner. My updated spec_helper.rb file looks like this, and now everything works:
# Clean up the database
require 'database_cleaner'
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
Simple example might be a Post, that has three states, DRAFT, PUBLISHED and DELETED.
The way I do this right now is something like:
class Post < ActiveRecord::Base
DRAFT = 0
PUBLISHED = 1
DELETED = 2
end
The problem that arises is that when I'm running my tests using spork, I have to reload the model manualy, with something like
Spork.each_run do
Dir["#{Rails.root}/app/models/**/*.rb"].each { |model| load model }
end
Which in result gives me loads of warnings like
warning: already initialized constant DRAFT
warning: already initialized constant PUBLISHED
warning: already initialized constant DELETED
Everything works just fine, but I don't think this is the best way to do this. Is there a better way to do this? I know there are gems like acts_as_state_machine, but I'd like to know a non-gem solution if there is a simple one.