Rails find_or_create_by where block runs in the find case? - ruby-on-rails

The ActiveRecord find_or_create_by dynamic finder method allows me to specify a block. The documentation isn't clear on this, but it seems that the block only runs in the create case, and not in the find case. In other words, if the record is found, the block doesn't run. I tested it with this console code:
User.find_or_create_by_name("An Existing Name") do |u|
puts "I'M IN THE BLOCK"
end
(nothing was printed). Is there any way to have the block run in both cases?

As far as I understand block will be executed if nothing found. Usecase of it looks like this:
User.find_or_create_by_name("Pedro") do |u|
u.money = 0
u.country = "Mexico"
puts "User is created"
end
If user is not found the it will initialized new User with name "Pedro" and all this stuff inside block and will return new created user. If user exists it will just return this user without executing the block.
Also you can use "block style" other methods like:
User.create do |u|
u.name = "Pedro"
u.money = 1000
end
It will do the same as User.create( :name => "Pedro", :money => 1000 ) but looks little nicer
and
User.find(19) do |u|
..
end
etc

It doesn't seem to me that this question is actually answered so I will. This is the simplest way, I think, you can achieve that:
User.find_or_create_by_name("An Existing Name or Non Existing Name").tap do |u|
puts "I'M IN THE BLOCK REGARDLESS OF THE NAME'S EXISTENCE"
end
Cheers!

Related

find_or_create by, if found, does it update?

https://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by
After reading the docs, it does say: "find the first user named "Penélope" or create a new one." and "We already have one so the existing record will be returned."
But I do want to be 100% clear about this .
If I do:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
end
and User does exist with both first_name: 'Scarlett' and `last_name: 'Johansson'``, will it update it or completely ignore it?
In my case, I would like to completely ignore it if it exists at all and wondering if find_or_create is the way to go. Because I don't want to bother updating records with the same information. I am not trying to return anything either.
Should I be using find_or_create, or just use exists?
Also, if find_or_create does act as a way to check if it exists and would ignore if it does, would I be able to use it that way?
For example:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
puts "Hello" #if it doesn't exist
end
Would "Hello" puts if it doesn't exist and not puts if it does?
In the example, if you have one or more User records with the first name 'Scarlett', then find_or_create_by will return one of those records (using a LIMIT 1 query). Your code, as provided, will set - but not save - the last_name of that record to 'Johansson'.
If you do not have one or more records with the first name 'Scarlett', then a record will be created and the field first_name will have the value 'Scarlett'. Again, the last_name field will be set to 'Johansson', but will not be saved (in the code you provide; you might save it elsewhere).
In this code:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
puts "Hello" #if it doesn't exist
end
...you will always see "Hello" because find_or_create_by will always return a record (either a found one or a created one).

Rails -- Export CSV failing if there is a blank field

I have code in my Rails app that allows me to export a CSV file. It works fine unless there is a record that has a field with no value in it. In that case it fails. As an example, the specific failure I'm getting is saying something liek "No Method Error" and it specifically references "address_line_1" because there are some users with no address_line_1. That is just one example though. Really all fields should be protected against potential blanks. Here is the code:
def download_kids_csv
#csv_headers = ['First',
'Last',
'Child First',
'Child Last',
'Parent Email',
'School',
'Class',
'Address',
'City',
'State',
'Zip',
'Parent Phone']
#kid_data = []
#school = School.find(params[:school_id])
#school.classrooms.each do |classroom|
classroom.kids.includes(:users).each do |kid|
kid.users.each do |parent|
#kid_data << {
first: parent.first_name,
last: parent.last_name,
child_first: kid.first_name,
child_last: kid.last_name,
parent_email: parent.email,
school: #school.name,
class: classroom.classroom_name,
address: parent.addresses.first.address_line_1,
city: parent.addresses.first.city,
state: parent.addresses.first.state,
zip: parent.addresses.first.zip_code,
parent_phone: parent.phones.first.phone_number
}
end
end
end
respond_to do |format|
format.csv do
headers['Content-Disposition'] = "attachment; filename=\"#{#school.name.downcase.gsub(' ', '-')}-data.csv\""
headers['Content-Type'] ||= 'text/csv'
end
end
end
Ok so the problem you are get is because you are calling method on a nil value.
So for example when you do:
kid.first_name
and kid is nil you are doing this
nil.first_name
nil does not implement the first_name method so it throws an error. WHat you could do to circumvent this (its kinda ugly) is this
kid.try(:first_name)
This will prevent you form getting those method missing errors
For those long chains you can do the following
parent.try(:addresses).try(:first).try(:zip_code)
This should save you a lot of headache, but the root cause of your issue is data integrity you would not have to do all of this if you ensured that your data was not blank. I do however understand in the real world it easier said than done. I could give you a lecture about The Law of Demeter and how you should not be running across object to access their attributes, and how thats a code smell of bad organization of data, but its a spread sheet and sometimes you just need the data. Good luck!
To build off of the earlier answer, you can also utilize the so-called lonely operator &. if you're on Ruby 2.3.
An example would look something like this: kid&.first_name.
If you're not on that version of ruby yet, there's a good gem that can help you out in this situation that's a little bit more robust than .try.
Using that gem your code would look like kid.andand.first_name. It might be overkill in this case but the difference here is that it will only perform the first_name method call if kid is not nil. For your longer chains, parent.address.first.zip_code, this would mean that the function chain would exit immediately if parent was nil instead of calling all of the different attributes with try.
Is it possible to use unless or another conditional?
unless parent.addresses.first.address_line_1.blank?
address: parent.addresses.first.address_line_1,
end
or
if parent.addresses.first.address_line_1 != nil
address: parent.addresses.first.address_line_1,
else
address: nil || "address is empty"
end

Rails rspec after_save reference self

I want to add a profanity check on my website.
I'm taking a TD approach and I'm trying the following:
check if profanity exists in specific profile fields
create a flag
create a flag if one does not exist
create a flag if one exists, but has been dismissed
Here is my spec so far:
describe Painter do
before do
#painter = FactoryGirl.create(:painter_flag)
end
context "blacklist flag" do
it "check if profanity exists" do
#painter.experience = "test"
#painter.save
expect {#painter.blacklist_flags?}.to be_true
end
it "create flag if profanity exists" do
#painter.experience = "test"
#painter.save
BlacklistFlag.count.should be > 0
end
end
end
Painter related code:
after_save :create_flag, if: :blacklist_flags?
def blacklist_flags?
list = ""
list << skills
#list << experience
#list << first_name
#list << last_name
#list.downcase.scan(/(badword|badword)/).size > 0
end
def create_flag
end
If I comment out the following code above the two test pass:
list << skills
When I leave the code in I receive the following error:
2) Painter blacklist flag create flag if profanity exists
Failure/Error: #painter = FactoryGirl.create(:painter_flag)
TypeError:
can't convert nil into String
It seems there's a problem with referencing self because skills, experience, etc are part of the model. I'm not sure how to fix this. Please advise.
Update:
FactoryGirl.define do
factory :painter do
first_name "Brian"
last_name "Rosedale"
state "OH"
zip_code "43081"
sequence(:email) {|n| "nobody#{n}#painterprofessions.com" }
phone "12345566"
pdca_member false
password "123456"
factory :painter_flag do
skills = "badword"
end
end
end
Just use this line in your factory for :painter_flag, without the = sign.
skills "badword"
I think what's causing the error is because the callback is been executed on the line #painter = FactoryGirl.create(:painter_flag).
You might want to use FactoryGirl.build method if you want to test the callback.

assigning values to model

I'm kinda new to coding on rails. It would be great if you could help me out with what I think might be noob question.Here's my code:
def create
#project = Project.new(params[:project])
if #project.save
redirect_to new_project_path
end
student=#project.student_str.split(";")
#users = User.where(:code => student)
#users.each do |c|
puts c.email
end
#users.each do |c|
puts "I'm here"
c.projects = "#{c.projects};#{#project.id}"
end
end
So, in the create method, Each time a new project is created a string called student_str is stored where the ID number of each student is seperated by a ";". I split that string to an array using the split function to get an array of student ID's. I have the puts c.email and puts "I'm here" to make sure the loops are working fine. I get the proper outputs on terminal.
The problem here is the
c.projects = "#{c.projects};#{#project.id}"
That simply does not seem to be working.
My model is not updated when this line is executed. I get no errors though.
Can you tell me what I might have to do to fix this?
thanks!
You have to call c.save after you updated the projects attribute. Otherwise the object is updated but not the database so the next time you load it the changes are gone.

Iterating through every record in a database - Ruby on Rails / ActiveRecord

n00b question. I'm trying to loop through every User record in my database. The pseudo code might look a little something like this:
def send_notifications
render :nothing => true
# Randomly select Message record from DB
#message = Message.offset(rand(Message.count)).first
random_message = #message.content
#user = User.all.entries.each do
#user = User.find(:id)
number_to_text = ""
#user.number = number_to_text #number is a User's phone number
puts #user.number
end
end
Can someone fill me in on the best approach for doing this? A little help with the syntax would be great too :)
Here is the correct syntax to iterate over all User :
User.all.each do |user|
#the code here is called once for each user
# user is accessible by 'user' variable
# WARNING: User.all performs poorly with large datasets
end
To improve performance and decrease load, use User.find_each (see doc) instead of User.all. Note that using find_each loses the ability to sort.
Also a possible one-liner for same purpose:
User.all.map { |u| u.number = ""; puts u.number }

Resources