Best way to do a find_by_or_create based on an attribute?

I have a form for @topic that contains two fields, name(string) visible(boolean)

When the user submits the form, I want to manipulate the contents of the attribute :name (which I am able to do - but it's the next part I'm stuck on)

-and then- see if a record with the contents of :name already exists

-if it already exists- I want to simple add my user association to it (which I can do by: current_user.topics << @topic)

-if it does not exist- then I want to create it as normal

find_or_create_by_name(params:topic) doesn't seem to work... are there any other ways to achieve this?

(nube alert!)

Thanks in advance.

If it were me I'd be clear about what I'm doing...

name = params[:topic][:name] # manipulate name here... @topic = Topic.find_or_create_by_name(name) current_user.topics << @topic

Only thing i'm not sure about is what you want to do with 'visible'...

-philip

Aston J. wrote in post #968740:

I have a form for @topic that contains two fields, name(string) visible(boolean)

When the user submits the form, I want to manipulate the contents of the attribute :name (which I am able to do - but it's the next part I'm stuck on)

-and then- see if a record with the contents of :name already exists

-if it already exists- I want to simple add my user association to it (which I can do by: current_user.topics << @topic)

-if it does not exist- then I want to create it as normal

This is so I don't end up with duplicate 'topics' in my DB.

find_or_create_by_name(params:topic) doesn't seem to work...

Did you mean find_or_create_by_name(params[:topic][:name])? And what do you mean by "doesn't seem to work"? In general, it's best if you describe what happened as clearly as possible.

are there

any other ways to achieve this?

(I'm a nube)

Thanks in advance.

Best,

Thanks for the reply both. Does this make it any clearer? It is my create action based on what Philip said - but it does not work: Topic(#2171041280) expected, got Hash(#2151972860)

  def hashtag(name)     string = name     return name = string if string.split.count <= 1     name = string.split.map{|w| w.capitalize}.join   end

  def create     name = params[:topic][:name]     visible = params[:topic][:visible]     hashtag(name)     @topic = {:name => "#{name}", :visible => visible}

    if       Topic.where(:name => "#{name}")       current_user.topics << @topic       redirect_to(@topic, :notice => 'Topic was successfully created.')     elsif       Topic.create(@topic)       current_user.topics << @topic       redirect_to(@topic, :notice => 'Topic was successfully created.')     else       render :action => "new"     end   end

View as pastie here: http://pastie.org/private/kbcez0wxhfvahimdtvu64g

Basically I am trying to manipulate the contents of :name and then check to see if a record already exists - to save duplicates.

Thanks for the reply both. Does this make it any clearer? It is my create action based on what Philip said - but it does not work: Topic(#2171041280) expected, got Hash(#2151972860)

You're trying to add a hash to an AR association... Rails doesn't like that :slight_smile:

def hashtag(name)    string = name    return name = string if string.split.count <= 1    name = string.split.map{|w| w.capitalize}.join end

Take a look at the titleize() method and get rid of hashtag entirely...

def create    name = params[:topic][:name]    visible = params[:topic][:visible]    hashtag(name)    @topic = {:name => "#{name}", :visible => visible}

What is visible? If visible is related to the user's ability to see that topic then it shouldn't be set in the topic itself. You'll need an intermediate model (look up has many through) to handle that. Ignoring that for now...

def create   name = params[:topic][:name].titleize   @topic = Topic.find_or_create_by_name(name)   current_user.topics << topic   redirect_to @topic, :notice => 'Topic was successfully created.' end

Hi Philip, I tried your code:

  def create     name = params[:topic][:name].titleize     @topic = Topic.find_or_create_by_name(name)     current_user.topics << @topic     if @topic.save       redirect_to @topic, :notice => 'Topic was successfully created.'     else       render :action => "new"     end   end

But now I get this if a topic has already been created:

'Validation failed: User has already been taken'

What is visible? If visible is related to the user's ability to see that topic then it shouldn't be set in the topic itself. You'll need an intermediate model (look up has many through) to handle that.

Visible will be used like a soft-delete, that only admin users will be able to modify. By default all topics are visible, but if a topic is against our rules :visible will be changed to false. That way it will not show (to site visitors) and it will also catch any future violations (as it's already been created).

Take a look at the titleize() method and get rid of hashtag entirely...

Titleize only capitalizes the first letter of each word... I want to remove the space too, so it because a #hastag :slight_smile:

Hi Philip, I tried your code:

def create    name = params[:topic][:name].titleize    @topic = Topic.find_or_create_by_name(name)    current_user.topics << @topic    if @topic.save      redirect_to @topic, :notice => 'Topic was successfully created.'    else      render :action => "new"    end end

But now I get this if a topic has already been created:

'Validation failed: User has already been taken'

That's not the code I sent... why are you trying to save a @topic that you either a) already found or b) just created (and saved) ??

If you are concerned the topic might not get created right then...

def create    name = params[:topic][:name].titleize    @topic = Topic.find_or_create_by_name(name)    if @topic      current_user.topics << @topic      redirect_to @topic, :notice => 'Topic was successfully created.'    else      render :action => "new"    end end

What is visible? If visible is related to the user's ability to see that topic then it shouldn't be set in the topic itself. You'll need an intermediate model (look up has many through) to handle that.

Visible will be used like a soft-delete, that only admin users will be able to modify. By default all topics are visible, but if a topic is against our rules :visible will be changed to false. That way it will not show (to site visitors) and it will also catch any future violations (as it's already been created).

Ok. Then you'll need to set that up as well in the above code after you've found the topic.

Hey... not sure why I thought of this before, but why is your create() method *finding* a topic at all? You might want to see if there's a better way to organize your actions...

Take a look at the titleize() method and get rid of hashtag entirely...

Titleize only capitalizes the first letter of each word... I want to remove the space too, so it because a #hastag :slight_smile:

Ah yes. Missed that...

name.titleize.gsub(/\s+/, '')

-philip

Philip Hallstrom wrote in post #968761:

...why are you trying to save a @topic that you either a) already found or b) just created (and saved) ??

Sorry I got confused for a second and thought the create action by default uses Topic.create() by default (when it actually uses Topic.new()).

If you are concerned the topic might not get created right then...

def create    name = params[:topic][:name].titleize    @topic = Topic.find_or_create_by_name(name)    if @topic      current_user.topics << @topic      redirect_to @topic, :notice => 'Topic was successfully created.'    else      render :action => "new"    end end

Thanks I tried that and it seems to be working, though it highlights a problem of when a user tries to submit the same topic. So here's a bashat my latest code (but it still gives me an error when the same user tries to create the same topic 'Validation failed: User has already been taken')

  def create     name = params[:topic][:name].titleize.gsub(/\s+/, '')     @topic = Topic.find_or_create_by_name(name)        if @topic          if current_user.topics << @topic            redirect_to @topic, :notice => 'Topic was successfully created.'          else            redirect_to @topic, :notice => 'You have already added this topic'          end        else          render :action => "new"        end     end

(see my next bit below to see what I'm trying to achieve)

Actually I have just noticed, I have a validation for the TopicAssociation model: validates_uniqueness_of :user_id, :scope => :topic_id so I guess that's why it is failing - but how can I handle that error more elegantly?

Ok. Then you'll need to set that up as well in the above code after you've found the topic.

Hey... not sure why I thought of this before, but why is your create() method *finding* a topic at all? You might want to see if there's a better way to organize your actions...

What I'm trying to do in all the code above is based on..

Having a page which will have a form with the fields 'topic' and 'comment' (I'll come to dealing with comments later, just trying to get topic working right now) and neither can be blank - Users are basically adding 'topics' that interest them (which will show on their profile), along with a comment about each topic. (And the homepage of the site will simply list the most popular topics.)

So when they click submit, I want to first check that the topic doesn't already exist (to prevent duplicate entries in the db - thus making it easier to find which topics are the most popular) and if it does, to simply add the association that they have added it (by 'current_user.topics << @topic' ?) (and then it will add the comment to that topic too, but will deal with that later). If the topic doesn't exist, it should get created, the association between user and topic be made, and their comment get added (again, I'll deal with comments later).

Does that make more sense about what I'm trying to do? (And why it's all happening from one form). I don't want to add a 'see if this topic exists first' page, as I would rather handle all that silently. Maybe later I could add a 'live search' for the topic name field - but that's a future feature lol

Ah yes. Missed that...

name.titleize.gsub(/\s+/, '')

Thanks! That's what I wanted - and I'll have to read up on what all of that does too :slight_smile: (btw, as therean easy enough to strip anything that isn't a-z or 1-0 from the string? That would help ensure records are unique regardless of punctuation marks etc)