Reputation: 257
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!
Upvotes: 1
Views: 652
Reputation: 7911
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
Upvotes: 0
Reputation: 11409
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.
Upvotes: 2