1

I currently have Scene and Role models, and a SceneRole association table joining them:

class Scene < ActiveRecord::Base
  has_many :scene_role
  has_many :roles, through: :scene_role
end

class Role < ActiveRecord::Base
  has_many :scene_role
  has_many :scenes, through: :scene_role
end

class SceneRole < ActiveRecord::Base
  belongs_to :scene
  belongs_to :role

  validates_presence_of :scene_id
  validates_presence_of :role_id

  validates_uniqueness_of :scene, :scope => :role
end

I want to ensure that any same scene-role relationship is unique. But I also want to gracefully handle attempts to add a Role to a Scene when the relationship already exists without getting an error: ActiveRecord::RecordInvalid: Validation failed: Scene has already been taken

My test code:

role = Role.new(name: "Big Bossman")
scene = Scene.new(name: "Arena")

scene.roles << role # success
scene.roles << role # Exception

Is it possible to override create with the behavior of first_or_create? I believe that would solve my problem. However, if there is a better way to accomplish the same result I'd appreciate any suggestions. Thank you!

Mike S
  • 11,329
  • 6
  • 41
  • 76
JP.
  • 257
  • 1
  • 3
  • 8

2 Answers2

2

It is possible with monkey patching but it's a very bad idea. You would be asking for major trouble down the road when you or someone else expects the code to behave in a certain default way. What if the requirements change and you remove the validation? You would silently never be able to create multiple records with create because first_or_create would always find the existing one.

The best alternative would be to check if the role already exists in scene.roles. For example:

scene.roles.include?(role) ? false : scene.roles << role
# does scene.roles include role? if yes: do nothing, if not: add role

Or do something like this.

Community
  • 1
  • 1
Mike S
  • 11,329
  • 6
  • 41
  • 76
  • I created an `add_role` method as you suggested and it's working great; no monkey patching required. Thank you for your help. – JP. Feb 29 '16 at 22:40
0

One way is to add this in a before_validation method that wouldn't actually raise an error, but would remove the previous role if it exists. In this you would be able to handle edge cases.

class SceneRole < ActiveRecord::Base
  before_validation :single_relation

  def single_relation
    # Checks new and stored records for duplicate and removes it
    # Might want to have this on both scene and role instead of here
  end
end
Tom Prats
  • 7,364
  • 9
  • 47
  • 77