find_or_create by, if found, does it update? - ruby-on-rails

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).

Related

Ruby on Rails beginner question : equality

I'm starting to know ROR and I was doing a kind of blog with articles, etc...
I did this code :
def show
id = params[:id]
list = Article.all
is_valid = false
list.all.each do |article|
if article.id == id
#is_valid = true
break
end
end
As you can see, this code just wants to check if the article ID exists or not. So I'm testing equality between id and article.id (which's a model linked to the appropriated table in the database) BUT when I try to use or display #is_valid boolean I saw that article.id == id is FALSE every time, even if article.id = 2 and id = 2. I tried to think about everything that can make this occuring, but I admit I still misunderstand this.
Then I ask you if you know why this is occuring. Of course, an equality like 2 == 2 will change #is_valid to true.
Thank you for your help !
Maybe its because params[:id] it's a string and article.id it's an Integer
(byebug) params
{"controller"=>"admin/my_controller", "action"=>"edit", "id"=>"1"}
And yes it is... "id" is a string "1", so you may try this:
def show
id = params[:id].to_i
list = Article.all
is_valid = false
list.all.each do |article|
if article.id == id
#is_valid = true
break
end
end
end
And maybe could work.
This is the answer to your question,
But if you want to learn a little more about Activerecord you can do this
Article.exists?(params[:id])
and that will do what you are trying to do just with a query against db.
and if you want to get just a simple article
record = Article.find_by(id: params[:id]) #return nil when not exist
if record # if nil will threat like false on ruby
#my code when exist
else
#my code when not exist
end
will work (you also can use find but find will throw an exception ActiveRecord::RecordNotFound when not exists so you have to catch that exception.
Activerecord has many ways to check this you dont need to do it by hand.
def show
#article = Article.find(params[:id])
end
This will create a database query which returns a single row. .find raises a ActiveRecord::NotFound exception if the record is not found. Rails catches this error and shows a 404 page. Article.find_by(id: params[:id]) is the "safe" alternative that does not raise.
Your code is problematic since list = Article.all will load all the records out of the database which is slow and will exhaust the memory on the server if you have enough articles. Its the least effective way possible to solve the task.
If you want to just test for existence use .exists? or .any?. This creates a COUNT query instead of selecting the rows.
Article.where(title: 'Hello World').exists?

How to change structure of existing columns, based on old values during migration?

During development, structure of column has been changed, so it's needed to adopt old users data to new format in production. It looks like issue that can be solved by migration. The problem is, I'm not an experienced ruby specialist, so it would be great to have advice how to implement it.
To make things clear, I'll give an example of what happened in my project.
There is table users. This table contains next columns,
id
user_type
description
description here is just JSON string that looks like that in old implementation,
first_name
last_name
address
After changes, instead of first_name and last_name we have full_name, only for users with type 'customer'.
So, how can I migrate my old data to new format? Thanks.
Your respective model User must have following,
serialize :description, Hash
Try to write rake in below path,
lib/tasks/update_users.rake
namespace :update_users do
desc 'Update description for full name for all user'
task update_description: :environment do
User.all.each do |user|
user.description[:full_name] = user.description.delete(:first_name) + ' ' + user.description.delete(:last_name)
user.save(validate: false)
end
end
end
And run rake as, rake update_users:update_description
Perhaps you can run code through rails console,
User.all.each do |user|
user.description[:full_name] = user.description.delete(:first_name) + ' ' + user.description.delete(:last_name)
user.save(validate: false)
end

How to make my seeds file Idempotent?

In my deployment process I am running my seeds file. I want this to be Idempotent so I can run it multiple times without any issue.
Currently I get PG primary key errors if I run it multiple times.
My seeds pattern looks like this:
user = User.create(.....)
user.save!
foo = Foo.create(....)
foo.save!
How can I make this Idempotent?
Is this the best way?
if( user.exists?(some_column: some_value) )
else
# do insert here
end
I believe you can make use of first_or_create
User.where(email: "email#gmail.com").first_or_create do |user|
user.name = "John"
end
This will only create User with email = "email#gmail.com" if it doesn't exist or it will return you the instance of existing User.
This way you can avoid the Unique Key Violation
You can try :
unless user.find_by(some_column: some_value)
user.save!
end

How to compare new data with existing in bd before save

Every time, when I'm getting the data from the API request, I require to compare and update records, if any changes was there.
for example i have saved User
user = User.first
user.name => 'name_one'
when i calling to api,api returns me User but name was cahnged to 'name_two'
so i need compare existing user with newly arrived and if name changed replace it
example of calling api
url= 'my api str'
result = Curl.get(url)
JSON.parse(result.body_str).each do |key, value|
value["commissions"].each do |k, v|
User.create(name: v["name"],etc... )
end
end
I will be glad for any suggestions.
You can also try the following:
u = User.first
u.name = "name_two"
u.name_changed? #=> true/false
u.name_was #=> "name_one" if it was "name_one"
These are called dirty method and very helpful for this kind of task.
Thanks
Try this code
u = User.first
u.name = "name_two"
u.save if u.changes.include?("name")
I believe you shall use active record's private method #read_attribute to compare current updated values with stores ones:
if send(:read_attribute, :attr) == self.attr
# value isn't changed
end
Additional info you can read here:
Validation from controller
You shall validate newly created record from controller:
v['name'] = 'User 1'
user = User.create(name: v['name'])
user.name # => # User 1

Rails find_or_create_by where block runs in the find case?

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!

Resources