Elliot
Elliot

Reputation: 13835

How to check if a record exists before creating a new one in rails3?

Heres what I'm trying to accomplish:

I want to check if the tag already exists. If it does I want to use the existing tag for the tag_join record, rather than creating a new tag record.

Here is my current code, which isn't working.

class Tag < ActiveRecord :: Base
  belongs_to :user
  belongs_to :tag_join
  belongs_to :post

  before_create :check_exists

  def check_exists
    tag = Tag.where(:name => self.name, :user_id => current_user.id)
    if tag.nil?
      tag = Tag.create(:name => self.name, :user_id => current_user.id)
    end
  end

end

This doesn't work though, I'm getting an error upon task creation...(the server is actually just timing out - I don't receive a specific error).

Any ideas?

Tokland said I was creating an infinite loop by telling it to create tag again - so I tried this:

 def check_exists
      tag = Tag.find_by_name_and_user_id(:name => self.name, :user_id => current_user.id)
      if tag != nil
        self.id = tag.id
      end
  end

And still get the server timeout

Edit: I'm not sure if this matters, but the way the tags are being added is similar to "http://railscasts.com/episodes/73-complex-forms-part-1

they're nested in the post form, and use something like this:

def tag_attributes=(tag_attributes)
  tag_attributes.each do |attributes|
    tags.build(attributes)
  end
end

I'm wondering if this is stopping this whole thing from working? Also, using current_user.id in the model definitely seems to be an issue...

EDIT:

Something I have figured out: this had to change, the format we were using before was incorrect syntax - generally used for a .where method.

  def check_exists
     @tag = Tag.find_by_name_and_user_id(self.name, self.user_id) 
     if @tag != nil
       #return false
       #self=@tag
     end
  end

The problem now is this, I can learn if it the tag already exists. But then what? If I go with the return false option, there is an error upon post creation, and the join record isn't created... The other option "self=@tag" obviously just doesn't work.

Upvotes: 6

Views: 30567

Answers (7)

Elliot
Elliot

Reputation: 13835

The question I originally asked got pretty distorted by the end. So I'm separating it.

People who are trying to do what I originally asked can try this:

 before_create :check_tag_exists

 private

 def check_tag_exists
     @tag = Tag.find_by_name_and_user_id(self.name, self.user_id)
     if @tag != nil
       #
     end
  end

This will enable you to check if your record has already been created. Any further logic you can drop in that if statment.

Upvotes: 5

Bjorn
Bjorn

Reputation: 5362

There's a find_or_create_by_ function built right in to Rails

# No 'Summer' tag exists
Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")

# Now the 'Summer' tag does exist
Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")

http://api.rubyonrails.org/classes/ActiveRecord/Base.html (under Dynamic attribute-based finders)

Upvotes: 9

Kabir Sarin
Kabir Sarin

Reputation: 18526

I believe the other answers are a bit dated. Here's how you should probably accomplish this for Rails 4

tag = Tag.first_or_initialize(:name => self.name, :user_id => current_user.id)
if !tag.new_record?
    tag.id = self.id
    tag.save
end

Upvotes: 3

Peter Zensius
Peter Zensius

Reputation: 1

where returns an empty ActiveRecord on finding no match.

Upvotes: -2

rubyprince
rubyprince

Reputation: 17793

try this

  def check_exists
    tag = Tag.where(:name => self.name, :user_id => current_user.id).first
    tag = Tag.new({:name => self.name, :user_id => current_user.id}) unless tag
  end

use Tag.new instead of Tag.create

Upvotes: 0

zetetic
zetetic

Reputation: 47548

You're going to find it hard to to this from within the Tag model. It seems like what you want is to update the Post using nested attributes, like so:

post = Post.create
post.update_attributes(:tags_attributes=>{"0"=>{:name=>"fish",:user_id=>"37"}})

This is actually pretty simple to do by using a virtual attribute setter method:

class Post < AR::Base
  has_many :tags

  def tags_attributes=(hash)
    hash.each do |sequence,tag_values|
      tags <<  Tag.find_or_create_by_name_and_user_id(tag_values[:name],\
        tag_values[:user_id])
    end
  end

> post = Post.create
> post.update_attributes(:tags_attributes=>{"0"=>{:name=>"fish",:user_id=>"37"}})
> Tag.count # => 1
# updating again does not add dups
> post.update_attributes(:tags_attributes=>{"0"=>{:name=>"fish",:user_id=>"37"}})
> Tag.count # => 1

Upvotes: 12

Rob Di Marco
Rob Di Marco

Reputation: 44932

You want to use the magic method find_or_create_by

def check_exists
    tag = Tag.find_or_create_by_name_and_user_id(:name => self.name, :user_id => current_user.id)
end

Check out the ActiveRecord::Base docs for more info

Upvotes: 6

Related Questions