Best way to find_or_create_by_id but update the attributes if the record is found

前端 未结 7 1436
情书的邮戳
情书的邮戳 2021-01-06 03:50

I\'m looking for a clean way to create a record with a set of attributes if the record does not exist and - if the record do exist - to update its attributes. I love the syn

相关标签:
7条回答
  • 2021-01-06 03:56

    I liked fl00r's answer. But why do we have to save the object every time? We can check if its already there in records or else save it

    def self.find_or_create_by_id(id, &block)
        obj = self.find_by_id(id) 
        unless obj
          obj = self.create(id: id)
        end
        obj
    end
    
    0 讨论(0)
  • 2021-01-06 03:58

    Try this:

    c = Category.find_or_initialize_by_id(category.id)
    c.name = category.name
    c.save!
    

    This way you only save the instance once, instead of twice, if you called find_or_create_by_id (assuming it's a new record).

    0 讨论(0)
  • 2021-01-06 03:58

    I did this yesterday, wishing there was a way to do it in a one-liner.

    Ended up going with (using your code):

    c = Category.find_or_initialize_by_id(category.id)
    c.name = category.name
    c.save
    

    Perhaps there is a nicer way, but this is what I used.

    [Edit: use initialize instead of create to avoid hitting the DB twice)

    0 讨论(0)
  • 2021-01-06 04:07

    I have coded this finders that can be used for different scenarios.

    The most important thing is, that it removes the parameter :id on create and update.

    Creating a model with :id can cause problems with MySql or PostgreSQL because Rails uses the auto sequence number of the database. If you create new model instances with an :id you can get a UniqueViolation: ERROR: duplicate key value violates unique constraint.

    # config/initializers/model_finders.rb
    class ActiveRecord::Base
    
      def self.find_by_or_create(attributes, &block)
        self.find_by(attributes) || self.create(attributes.except(:id), &block)
      end
    
    
      def self.find_by_or_create!(attributes, &block)
        self.find_by(attributes) || self.create!(attributes.except(:id), &block)
      end
    
    
      def self.find_or_create_update_by(attributes, &block)
        self.find_by(attributes).try(:update, attributes.except(:id), &block) || self.create(attributes.except(:id), &block)
      end
    
    
      def self.find_or_create_update_by!(attributes, &block)
        self.find_by(attributes).try(:update!, attributes.except(:id), &block) || self.create!(attributes.except(:id), &block)
      end
    
    
      def self.find_by_or_initialize(attributes, &block)
        self.find_by(attributes) || new(attributes.except(:id), &block)
      end
    
    end
    
    0 讨论(0)
  • 2021-01-06 04:09

    I have been using this patten for seeds:

    Category.find_or_initialize_by(id: category.id).update |c|
      c.name = category.name
    end
    

    It works the same as Dale Wijnand's and Teoulas answers (only saves the instance once) but uses a block like in your question.

    0 讨论(0)
  • 2021-01-06 04:16

    You can write your own method:

    class ActiveRecord::Base
      def self.find_by_id_or_create(id, &block)
        obj = self.find_by_id( id ) || self.new
        yield obj
        obj.save
      end
    end
    

    usage

     Category.find_by_id_or_create(10) do |c|
       c.name = "My new name"
     end
    

    Of course, in this way you should extend method missing method and implement this method in the same way as others find_by_something methods. But for being short this will be enough.

    0 讨论(0)
提交回复
热议问题