JP.
JP.

Reputation: 257

Can I override "create" with "first_or_create"?

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

Answers (2)

Tom Prats
Tom Prats

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

Mike S
Mike S

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

Related Questions