I have an album which has_many photos. A counter_cache setup updates the photos_count column in the album table. How do I limit the number of photos for an album?
Limit number of objects in has_many association
Asked Answered
In my case, it was sufficient to use validates_length_of
:
class Album
has_many :photos
validates_length_of :photos, maximum: 10
end
class Photo
belongs_to :album
validates_associated :album
end
This is the cleanest solution –
Batwing
Can confirm this works really well. Only problem is if you go the other way and just start creating a bunch of "Photos" that belong to album the same cannot be said. You will need to add :validate => true to the belongs_to, though I haven't tested to make sure this will always work as intended. –
Gillan
actually
belongs_to :album, validate: true
according to api.rubyonrails.org/classes/ActiveRecord/Associations/… checks associations on the parent save, not on child save... so you can add as many children as you want, and this could be a problem –
Osorio No need of validates_associated :album in Photo model. It will work without validates_associated as well. –
Snubnosed
I could create 11 photos using this code –
Pled
Use a validation hook:
class Album
has_many :photos
validate_on_create :photos_count_within_bounds
private
def photos_count_within_bounds
return if photos.blank?
errors.add("Too many photos") if photos.size > 10
end
end
class Photo
belongs_to :album
validates_associated :album
end
Thanks for the advice guys. I have got Marcel's code working. –
Theresa
photos.size is a better way to go - blog.hasmanythrough.com/2008/2/27/count-length-size, from a similar question - https://mcmap.net/q/269704/-validate-the-number-of-has_many-items-in-ruby-on-rails –
Tabethatabib
Amended as you suggested, @Tabethatabib –
Gourami
this solution still have a problem, you can add as many children, as you want with
album.photos.create
, at least with Rails 5.1.4, so it seems @Marcel Jackwerth sulution probably the best from this point of view –
Osorio @Osorio Yes, because it only validates on creation of the parent model. You could validate always by removing
_on_create
. –
Gourami @Gourami I've checked it without
_on_create
, but with parent.children.create
call from console I still can create one more child
and exceed the limit by 1 (at least for my polymorphic association)... I suppose the problem here is with the photos.size > 10
- on the moment of validation it is valid and became invalid just after the new photo
creation... it could be only rails console issue though, I didn't check if it shows the same behavior then calling create
from an application code –
Osorio @Osorio Maybe try
photos.length
, and maybe change has_many :photos, touch: true
so it updates the parent object and reruns the validations defined there. PS: The solution of @Hallmark may be the better one with current Rails versions, it may still need touch
. –
Gourami How about adding a custom validation method to the Photo model?
LIMIT = 50
validate_on_create do |record|
record.validate_quota
end
def validate_quota
return unless self.album
if self.album.photos(:reload).count >= LIMIT
errors.add(:base, :exceeded_quota)
end
end
thanks @Marcel Jackwerth! spend some time in
rails console
checking all 3 solutions, found out yours is the only which really preventing from creating "unwanted children" )) –
Osorio This solution worked best for my similar scenario. Glad you included
.reload
in your solution. I initially omitted it, and found some (but not all) scenarios would use an outdated count, allowing excessive child records to be created. –
Lanni ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute('LOCK TABLE pictures IN EXCLUSIVE MODE')
if (@album.pictures.count < 10)
@album.pictures.create()
end
end
I believe this is the most correct solution. It guards against concurrency issues/race conditions.
I would add a
reload
in there to ensure ActiveRecord isn't giving you stale results. Something like @album.pictures.reload.count < 10
–
Lanni © 2022 - 2024 — McMap. All rights reserved.
validates_associated
allow you to create children without limit usingparent.children.create
– Osorio