Query as a conditional statement in Ruby/Rails - ruby-on-rails-3.1

Was following the Depot application from Agile Web Development with Rails.
There was a method I got confused. I thought I understood it until I tried it in irb. So here's the method:
def add_product(product_id)
current_item = line_items.find_by_product_id(product_id)
if current_item
current_item.quantity += 1
else
current_item = line_items.build(product_id: product_id)
end
current_item
end
From what I understood, It's just a method that will first find a record in LineItems with a product ID of an give input (let's say it's 10). Then it will store it in current_item variable. The condition says 'If the product id was found, add 1 to quantity else create a new instance of that record with the product id equals to 10'
Here's the snapshot of my rails console
As you can see, product id of 10 in LineItem is not found. But on my condition, it goes against everything that I believe until now. Could someone shed a light on this?

Looks like line is an empty collection (ActiveRecord::Relation to be exact) and so it's something in Ruby and not nil. That's why it's returning true when you're calling if line and executing puts 'I'm true and happy'.The reason it's an ActiveRecord::Relation is because you're using the where query.
In the Depot application they're making the query by calling line_items.find_by_product_id(product_id) which is different. It finds the first record matching the condition.
Check out the Rails guides here for more info - http://guides.rubyonrails.org/active_record_querying.html (section 15)
PS Looks like that type of query is deprecated in Rails 4 so not sure what version of Agile Web Development with Rails you're looking at.

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.

Detect multiple conditions

I'm trying to create a simple wine web app with Sinatra. The keys within my Wine model are "vintner", "vintage", and "varietal". Vintage is an integer. I also have a Note model for users to add in notes (currently separating by commas, will plan to get more robust later...).
Here is my POST action within my WineController:
post '/wines' do
if params[:wine] == ""
erb :'wines/new'
else
#wine = current_user.wines.new(params[:wine])
#wines = current_user.wines
if #wines.detect{ |wine| wine.vintner.downcase ==
#wine.vintner.downcase && wine.varietal.downcase ==
#wine.varietal.downcase && wine.vintage == #wine.vintage }
flash[:message] = "That wine is already in your cellar! Add another."
erb :'/wines/new'
elsif !params[:note][:name].empty?
params[:note][:name].split(", ").each{ |user_note| #wine.notes <<
Note.find_or_create_by(:name => user_note) }
end
end
#wine.save
redirect to "/wines"
end
What I'm trying to accomplish is to say "if there is already a wine with that vintner, varietal, and vintage, don't create it and redirect back to the 'new' view with said message. Otherwise, add the notes to the that wine instance, save, and redirect to '/wines/index'".
Instead, though, the wine saves (the notes are not being saved, though) and I get redirected to '/wines/index' with said message. So, that's weird.
My main question is, how can I detect (using detect or any other method) based on three or more criteria. Also, if anyone has any insight into what I'm doing wrong with my notes, I'd love to hear! Thank you!!
As it currently is written the lines
#wine.save
redirect to "/wines"
are outside all the conditionals at the very bottom of the block, so that code will run, even if the condition params[:wine] == "" is met, and in that case it will error out because #wine doesn't get set.
Try moving that code into maybe the elsif !params[:note][:name].empty? branch and then it should only run when you need to create a new wine.

weird behaviour in model (ruby on rails 4)

ive weird behaviour in my model code,
but iam not sure if this is my problem or is it some weird issue with ruby on rails.
ive a associated
has_many :chat_user #linked to ChatUser
and ive defined
Class A
def guest
chat_user #returning chat_user from has_many
end
end
ive 2 records of ChatUser which is linked to this model class
when i called object of Chat A .chat_user -> count and each return 2 records
when i called object of .guest -> count return 2, but each loop only once!
what could be the issue?
thank you
using rails 4.0.0 with puma server
I found the issue, I've actually returned a custom where query with limit 1 applied to chat_user, hidden in a function.

PageObject with Ruby - set text in a text field only works in the main file

I'm automating a site that has a page with a list of options selected by a radio button. When selecting one of the radios, a text field and a select list are presented.
I created a file (test_contracting.rb) that is the one through which I execute the test (ruby test_contracting.rb) and some other classes to represent my page.
On my class ContractPage, I have the following element declaration:
checkbox(:option_sub_domain, :id => "option_sub_domain")
text_field(:domain, :id => "domain_text")
select_list(:tld, :id => "domain_tld")
I've created in the ContractPage a method that sets the configuration of the domain like this:
def configure_domain(config={})
check_option_sub_domain
domain = config[:domain]
tld = config[:tld]
end
When I call the method configure_domain from the test_contracting.rb, it selects the radio button, but it doesn't fill the field with the values. The params are getting into the method correctly. I've checked it using "puts". Even if I change the params to a general string like "bla" it doesnt work. The annoying point is that if on test_contracting.rb I call the exact same components, it works.
my_page_instance = ContractPage.new(browser)
my_page_instance.domain = "bla"
my_page_instance.tld = ".com"
What I found to work was to in the configure_domain method, implement the following:
domain_element.value = config[:domain]
tld_element.send_keys config[:locaweb_domain]
Then it worked.
The documentation for the PageObjects module that I'm using as reference can be found here: http://rubydoc.info/github/cheezy/page-object/master/PageObject/Accessors#select_list-instance_method
Do you guys have any explation on why the method auto generated by the pageobject to set the value of the object didnt work in this scope/context ?
By the way, a friend tried the same thing with Java and it failed as well.
In ruby all equals methods (methods that end with the = sign) need to have a receiver. Let me show you some code that will demonstrate why. Here is the code that sets a local variable to a value:
domain = "blah"
and here is the code that calls the domain= method:
domain = "blah"
In order for ruby to know that you are calling a method instead of setting a local variable you need to add a receiver. Simply change your method above to this and it will work:
def configure_domain(config={})
check_option_sub_domain
self.domain = config[:domain]
self.tld = config[:tld]
end
I'm pretty new to this world of Selenium and page objects but maybe one of my very recent discoveries might help you.
I found that that assignment methods for the select_list fields only worked for me once I started using "self" in front. This is what I have used to access it within my page object code. e.g., self.my_select_list="my select list value"
Another note - The send_keys workaround you mention is clever and might do the trick for a number of uses, but in my case the select list values are variable and may have several options starting with the same letter.
I hope something in here is useful to you.
UPDATE (Jan 3/12)
On diving further into the actual Ruby code for the page object I discovered that the select_list set is also using send_keys, so in actuality I still have the same limitation here as the one I noted using the send_keys workaround directly. sigh So much to learn, so little time!

Mongoid has_and_belongs_to_many associations

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.

Resources