0

Ok i have a model named Tire and I cant update the name field on some reserved names

class Tire < ActiveRecord::Base

  RESERVED_TIRES = ['Michelin', 'Good Year', 'Firestone']

  before_update :reserved_tires

  def reserved_tires
    if RESERVED_TIRES.include?(self.name)
      self.errors.add(:base, "Cant be changed")
      false
    end
  end
end

And i need to not allow the user to update any field is the current name is in the reserved words...this works for all fields other then when the user updates name.

For example is the user updates to "Michelinnnn" then it allows the update because self.name is "Michelinnnn" rather then 'Michelin' which is saved in the DB. Any ideas on how to address this

Matt Elhotiby
  • 43,028
  • 85
  • 218
  • 321

4 Answers4

3

If you mean you don't want to allow any change if the name before the change was reserved, then you can access the old name as name_was.

Leventix
  • 3,789
  • 1
  • 32
  • 41
  • would i use it like self.name_was – Matt Elhotiby Apr 15 '12 at 17:53
  • you don't need `self.`, because you're already in the object's context, just `name_was`. You can also remove it from before `errors`. You would need it when using a setter like `self.name = "something"`, because otherwise Ruby would set the local variable name and not call the method `name=(value)` on the object. – Leventix Apr 15 '12 at 17:57
1

So the problem is strings that are similar, but not identical, to the reserved names? The "michelinnnn" example would be caught by using regular expressions instead of exact string matches:

RESERVED_TIRES = [/michelin/i, /good\s*year/i, /firestone/i]
...
if RESERVED_TIRES.find{|r| self.name =~ r}

The built-in Rails function is validates_exclusion_of, but I don't know if that can handle (arrays of) regular expressions.

However, that will only catch certain types of similar names. There are more general ways to calculate string similarity, but there is no watertight solution for this kind of problem.

Community
  • 1
  • 1
rcrogers
  • 2,281
  • 1
  • 17
  • 14
1

First of all, you probably want to use a validation instead of your before_update:

class Tire < ActiveRecord::Base
  RESERVED_TIRES = ['Michelin', 'Good Year', 'Firestone']
  validate :reserved_tires, :unless => :new_record?

private

  def reserved_tires
    if RESERVED_TIRES.include?(self.name)
      self.errors.add(:base, "Cant be changed")
    end
  end
end

The :unless => :new_record? skips the validation for new records so you will be able to create them but changes will be prevented.

Then add another validation to catch them trying to change the name:

validate :cant_change_reserved_name, :if => :name_changed?
#...
def cant_change_reserved_name
  if RESERVED_TIRES.include?(self.name_was)
    self.errors.add(:name, "You cannot change a reserved name.")
  end
end
mu is too short
  • 426,620
  • 70
  • 833
  • 800
1

THis should do the trick:

name_allowed = RESERVED_TIRES.inject(true) { |is_allowed, tire_name|
  is_allowed &&= !self.name.include?(tire_name)
}
unless name_allowed
  # add error
end
emrass
  • 6,253
  • 3
  • 35
  • 57
  • Sure, just have a look at http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-inject to understand the inject better. Basically, it loops over all values in RESERVED_TIRES and puts the current record value in the second argument of the block (tire_name). The first argument is a boolean value in this example and is what the inject method returns. It's initialized with true. is_allowed &&= ... is the same as is_allowed = is_allowed && ..., which will always return false once the second validation (the one for the tire name) returned false in a previous loop instance – emrass Apr 15 '12 at 18:50