user3868832
user3868832

Reputation: 620

rails check for uniqueness and deal with condition

So I understand that i can write

validates :column_name, uniqueness: :true

and I can test to see if a field is unique. But if it isn't the above application throws an error. Instead of throwing an error I want to be able to make it unique with the addition of an integer.

The attribute is called 'legacy_code'. The code is generated by the first letters of the title wording and is called to action via

  after_create :generate_article_legacy_code

So the test I have written is like this

@article = FactoryGirl.create :article, title: 'Big Cheesey Pudding Yum Yum'
@article_one = FactoryGirl.create :article, title: 'Blue Chaffinches Purchased Yoko Yamamoto' 

  it "should be unique" do 
    expect(@article.legacy_code).to eq('BCPYY')
    expect(@article_one.legacy_code).to eq('BCPYY1')

  end

So how do I implement this kind of functionality in the article class?

Thanks

Upvotes: 1

Views: 130

Answers (3)

CleoR
CleoR

Reputation: 815

Well what if there are multiple copies of an article's title's acronym? For instance say there are 3 copies, the title_acronym wouldn't be unique on the second or third find and you would end up with ABC, ABC1, ABC2. That's what you want. But you find an issue if the title ends in a number. You could have (ABC1), (ABC1)1, (ABC1)2 ... and so forth but when you want to increment the acronym you can't tell if it's 1 or 11 without doing some work before hand. This is going to get messy.

I would make the title and a column called number unique together. So in a Rails 4 model you would have something like this

validates :title_acronym, uniqueness: {scope: :number}

Whenever an article with the same title_acronym pops up, that article will just need to set it's own number counter appropriately. And you can create an attribute on the model called legacy_code that returns the union of these two which is what you want. The full code if below.

validates :title, presence: :true
validates :title_acronym, presence: true
validates :number, presence: true 
validates :legacy_code, presence: true
validates :title_acronym, uniqueness: {scope: :number}

before_validate :setupArticle

def setupArticle
  generate_title_acronym
  generate_number
  generate_legacy_code
end

def generate_title_acronym
  if self.title.nil?
    return nil
  end
  #Code to set the title_acronym from the title
end

def generate_number
  if self.title_acronym.nil?
    return nil
  end
  self.number = Article.where(title_acronym, title_acronym).maximum(:number) || 0
end

def generate_legacy_code
  if self.title_acronym.nil? || self.number.nil?
    return nil
  end
  if self.number == 0
     self.legacy_code = "#{self.title_acronym}"
  else
     self.legacy_code = "#{self.title_acronym}#{self.number}"
  end
end

Uniqueness can also be enforced at the DB level but that may not be necessary.

Now there is a fundamental problem with this if you want to find a single record by its legacy_code. Say there is title_acronym ABC and another title_acronym ABC1. Since those are the first of their title_acronym, their title_acronym and legacy_code are the same. If there is another article with the title_acronym ABC, its legacy_code would have to become ABC1 but that isn't unique because that legacy_code already exists.

What this says is, if you can't guarantee titles don't end in numbers, you can't reliably search for a unique record using legacy_code because by it's nature legacy_code won't produce unique values. You need to search for a unique record using both title_acronym and number. I'm working on the assumption that titles can end in numbers since you are working with articles after all.

If you're willing to change how your legacy_codes look to gain uniqueness over them. You can add a delimiter in between title_acronym and number. I used the underscore in this example.

if self.number == 0
   self.legacy_code = "#{self.title_acronym}"
else
   self.legacy_code = "#{self.title_acronym}_#{self.number}"
end

legacy_codes are now guaranteed to be unique since it's made up of parts that are guaranteed to be unique and a delimiter but you can add this in for good measure.

validates :legacy_code, uniqueness: :true

Upvotes: 2

A Fader Darkly
A Fader Darkly

Reputation: 3626

You could try:

before_validate :generate_article_legacy_code

Ensure that if the code exists, the generate method does nothing.

Upvotes: 2

Amr Arafat
Amr Arafat

Reputation: 489

You can check whether the record is valid or not. If it is not vaild then add the integer and try again. Something like this:

if(@article.valid?)
  @article.save!
  # do whatever
else      
  # add integer
end

Upvotes: 0

Related Questions