How to make my seeds file Idempotent? - ruby-on-rails

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

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 5 ActiveRecord - check if any results before using methods

In my Rails 5 + Postgres app I make a query like this:
user = User.where("name = ?", name).first.email
So this gives me the email of the first user with the name.
But if no user with this names exists I get an error:
NoMethodError (undefined method `email' for nil:NilClass)
How can I check if I have any results before using the method?
I can think if various ways to do this using if-clauses:
user = User.where("name = ?", name).first
if user
user_email = user.email
end
But this does not seem to be the most elegant way and I am sure Rails has a better way.
You can use find_by, returns the object or nil if nothing is found.
user = User.find_by(name: name)
if user
...
end
That being said you could have still used the where clause if you're expecting more than one element.
users = User.where(name: name)
if users.any?
user = users.first
...
end
Then there is yet another way as of Ruby 2.3 where you can do
User.where(name: name).first&.name
The & can be used if you're not sure if the object is nil or not, in this instance the whole statement would return nil if no user is found.
I use try a lot to handle just this situation.
user = User.where("name = ?", name).first.try(:email)
It will return the email, or if the collection is empty (and first is nil) it will return nil without raising an error.
The catch is it'll also not fail if the record was found but no method or attribute exists, so you're less likely to catch a typo, but hopefully your tests would cover that.
user = User.where("name = ?", name).first.try(:emial)
This is not a problem if you use the Ruby 2.3 &. feature because it only works with nil object...
user = User.where("name = ?", name).first&.emial
# this will raise an error if the record is found but there's no emial attrib.
You can always use User.where("name = ?", name).first&.email, but I disagree that
user = User.where("name = ?", name).first
if user
user_email = user.email
end
is particularly inelegant. You can clean it up with something like
def my_method
if user
# do something with user.email
end
end
private
def user
#user ||= User.where("name = ?", name).first
# #user ||= User.find_by("name = ?", name) # can also be used here, and it preferred.
end
Unless you really think you're only going to use the user record once, you should prefer being explicit with whatever logic you're using.

Changing Spree from_address

I am trying to change Spree 3.0 from_email
I added this line to my spree initialiser, but it does not work:
Spree::Store.current.mail_from_address = “x#x.com"
Do you know of any reason why not?
I also put it directly in my mailer decorator:
Spree::OrderMailer.class_eval do
def confirm_email_to_store(order, resend = false)
Spree::Store.current.mail_from_address = "x#x.com"
#order = order.respond_to?(:id) ? order : Spree::Order.find(order)
subject = (resend ? "[#{Spree.t(:resend).upcase}] " : '')
subject += "#{'Will Call' if #order.will_call} #{'Just to See' if #order.just_to_see} ##{#order.number}"
mail(to: ENV['STORE_EMAIL'], from: from_address, subject: subject)
end
end
This also did not work
Check you might have created multiple stores via checking Spree::Store.all
Also, spree use current store as store which updated last so you have to check that also
You can simply change the from email address in the Admin Panel under Configuration -> General settings:
Got it to work this way:
Spree::Store.current.update(mail_from_address: ENV["STORE_EMAIL"])
Looking here http://www.davidverhasselt.com/set-attributes-in-activerecord/ you can see that:
user.name = "Rob"
This regular assignment is the most common and easiest to use. It is
the default write accessor generated by Rails. The name attribute will
be marked as dirty and the change will not be sent to the database
yet.
Of course, spree initializers claims to do save in the databse, but it did not:
If a preference is set here it will be stored within the cache & database upon initialization.
Finally calling Spree::Store.current will pull from the database, so any unsaved changes will be lost:
scope :by_url, lambda { |url| where("url like ?", "%#{url}%") }
def self.current(domain = nil)
current_store = domain ? Store.by_url(domain).first : nil
current_store || Store.default
end
Fixing this bug in Spree would be prefrable, this is sort of a workaround

How to populate empty attributes with random values from seeds.rb

I've generated User model with attributes: first_name, last_name, city. Then, I've created 200 instances of the class using seed.rb and gem 'faker'. After all, I have added one more attribute to this model - age:string, so now in every instance of the class the age = 'nil'. Now, I want to populate every single user with randomly generated number from range 10..99.
Here is my code from seed.rb:
users = User.all
users.each do |element|
element.age = rand(10..99).to_s
end
When I type rake db:seed it seems to be successfull but when I check the database, each age:string is still 'nil'. Do you have any idea what might have gone wrong?
You are assigning the value, but you don't save it. You should change your code to
users = User.all
users.each do |element|
element.age = rand(10..99).to_s
element.save!
end
or just
users = User.all
users.each do |user|
user.update_attribute(:age, rand(10..99))
end
If you don't need to validate the record, the following is even faster
users = User.all
users.each do |user|
user.update_column(:age, rand(10..99))
end
#Simone Carletti's answer is good... maybe you'd benefit from find_each:
User.find_each do |user|
user.update_attribute :age, rand(10..99)
end
Also, you can combine Faker with Fabrication for more cool seeds.
Faker::Name.name #=> "Christophe Bartell"
Faker::Internet.email #=> "kirsten.greenholt#corkeryfisher.info"
You can use it to generate random data from db/seed.rbrunning rake db:seed.

How to set up usernames from email of all users?

I would like to create rake task to set the username of all users' without a username to the part before the '#' in their email address. So if my email is test#email.eu, my username should become test. If it's not available, prepend it by a number (1).
So i have problem witch checking uniqness of username. Code below isn`t working after second loop ex: when i have three emails: test#smt.com, test#smt.pl, test#oo.com username for test#oo.com will be empty.
I have of course uniqness validation for username in User model.
desc "Set username of all users wihout a username"
task set_username_of_all_users: :environment do
users_without_username = User.where(:username => ["", nil])
users_without_username.each do |user|
username = user.email.split('#').first
users = User.where(:username => username)
if users.blank?
user.username = username
user.save
else
users.each_with_index do |u, index|
pre = (index + 1).to_s
u.username = username.insert(0, pre)
u.save
end
end
end
end
Other ideas are in Gist: https://gist.github.com/3067635#comments
You could use a simple while loop for checking the username:
users_without_username = User.where{ :username => nil }
users_without_username.each do |user|
email_part = user.email.split('#').first
user.username = email_part
prefix = 1
while user.invalid?
# add and increment prefix until a valid name is found
user.username = prefix.to_s + email_part
prefix += 1
end
user.save
end
However, it might be a better approach to ask the user to enter a username upon next login.
if i understand your code correct, you are changing the username of existing users in the else branch? that does not look as if it's a good idea.
you should also use a real finder to select your users that don't have a username. otherwise you will load all the users before selecting on them.
i don't know if it "matches your requirements" but you could just put a random number to the username so that you do not have the problem of duplicates.
another thing that you can use is rubys retry mechanism. just let active-record raise an error and retry with a changed username.
begin
do_something # exception raised
rescue
# handles error
retry # restart from beginning
end
In your query User.find_by_username(username), you only expect 1 record to be provided. So you don't need any each. You should add your index in another way.

Resources