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...

http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-titleize

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)