简体   繁体   中英

Rails: what is the best way to check if the record doesn't exist in the database?

My method loads a list of countries(code, name) into the database, but before that it has to check, if the country data does not already exist. This works fine:

 def self.load_countries
    get_countries.each do |country|
      code, name = country
      if find_by_code(code).nil?
        create({ 'name' => name, 'code' => code })
      end
    end
  end

However, as I am new to Ruby, I want to learn the best practises. So, in this code I am not sure about two things which might be (or might not be) optimised:

  1. find_by_attribute returns the "select * from table" statement. In this case, when I don't need any data from database - I just want to know if the record exists or not - selecting the entire row seems a little inefficient to me. Is there any better way solve this? For example, "select 1 from table where ..." using ActiveRecord?
  2. This question might be silly, but I want to be sure: when I start the loop with the get_countries.each, is it ok to use a method instead of a variable? Isn't the same method called each cycle (N times)? In other words, would this be anymore efficient:

    countries = get_countries

    countries.each do |country|

Any comments on those few lines of code are welcome, since the fact it works doesn't necessarily mean that I am doing it the right way.

Thank you.

You can use the exists? function in ActiveRecord.

def self.load_countries
  get_countries.each do |country|
    code, name = country
    unless exists?(:code => code)
      create({ :name => name, :code => code })
    end
  end
end

The get_countries function gets called only once. It returns an enumerable data type and then the each walks through each of them.

Use find_or_create_by

get_countries.each do |country|
  code, name = country
  find_or_create_by_code_and_name(code, name)
end

1) Add uniqueness validation to your model (assuming Rails 3)

validates :code, :uniqueness => true

Use db/seeds.rb for loading the seed data to database. IMHO 'load_countries' method doesn't belong to model (especially if it is a one time operation).

In Ruby on rails, we have four methods to check if a record exists in the database or not?

  1. .present?

    It is the most time-consuming method since it returns all records from the database.

  2. .any? / .empty?

    The above both have same performance efficiency since both of them finally fire a 'COUNT' query on the database. Hence this both are efficient when compared with .present?

  3. .exist?

    The last one is even more optimized, and it should be your first choice when checking the existence of a record. It uses the 'SELECT 1 ... LIMIT 1' approach.

Remember one thing when your ActiveRecord objects are already in memory (If you preloaded them) then do not use 'exist?' rather use 'any?', since 'exist?' always hit database in respect of whether the object is in-memory or not, whereas 'any? / empty?' fill not hit the database again if the records are already loaded into memory.

You can also refer to this article : Check if record is exist in ROR

Maybe you have to use UNIQUE for codes in your database model?

I mean this http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000086

You can use unique validation in your ActiveRecord model eg:

class Country < ActiveRecord::Base
  validates_uniqueness_of :code
  validates_uniqueness_of :name
end

1.) You don't necessarily need to select the whole row, and you could use the :select=> option to limit the columns you fetch, but this seems like a micro-optimization to me. I wouldn't worry about it. If the efficiency of this method bothers you that much, you'd be better off figuring out a way to avoid making an SQL query within a loop; for example, consider selecting all the existing countries before the loop, storing them in an array or hash, and using that to see if the country already exists. Then instead of dozens of trips to the database, you'd only make one (not counting those where you add new records). On the other hand, this doesn't sound like the kind of code you're going to be running lots of times (it sounds like a case of seeding a database table), so it might not matter much.

2.) No, the get_countries method won't get called each iteration of the loop, only once before the #each starts; Assuming get_countries returns an Array, #each is a method on the array.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM