I've got a model that looks like this:
class thing < ActiveRecord::Base
has_many :bobbles
validate :has_two_bobbles
def has_two_bobbles
unless self.bobbles.size == 2
errors.add(:bobbles, "Need two bobbles")
end
end
end
I'm running in to trouble when updating from a form. If I delete a bobble and add a bobble in the same submission, when I hit self.bobbles.size I get 3 and not 2. Is there anywhere to restrict self.bobbles to return only the records that arent's scheduled for deletion?
I know in the controller you have access to _destroy in the params, but is there anything at the model level that indicates if a record is going to be deleted?
Record is going to die when it responded to .marked_for_destruction?
class thing < ActiveRecord::Base
has_many :bobbles
validate :has_two_bobbles
def has_two_bobbles
unless self.bobbles.select {|t| !t.marked_for_destruction?}.size == 2
errors.add(:bobbles, "Need two bobbles")
end
end
end
Related
Seems like i’ve gone back to basics and am missing something… ..
I have two models User and Lists. User has many lists and lists belongs to User… When we remove the user from the list, i.e. the lists user_id == nil, we can’t seem to catch the update through a callback like after_update or after_commit.
class List < ApplicationRecord
after_update :check_user
belongs_to :user
private
def check_user
binding.pry
if user_id.blank?
end
end
end
we even tried before_save but it doesn’t seem like the model sees the change. Are we missing something obvious??
What method are you using to update the record? Because there are methods that DO NOT trigger callbacks like update_column, update_attribute and update_all
In my Rails 4 Postgres applicaiton I have a Project model and a WorkingDay model with a has_many relationship between them:
working_day.rb
class WorkingDay > ActiveRecord::Base
# working_times: text that is parsed into yml json format
belongs_to :project
end
and project.rb
class Project > ActiveRecord::Base
has_many :working_days
# total_cost: decimal based on calculations of working_times of each working_day
before_save :calculate_total_cost
def calculate_total_cost
# carry out certain calculations based on working_days of this project
self
end
def regen_working_days(wd_json_data)
self.working_days.each(&:destroy)
wd_json_data.each do |wd_data|
self.working_days.create(wd_data)
end
self.save!
self
end
end
The issue I am having is that once the regen_working_days is fired with the correct wd_json_data parameters, the total_cost is not calculated correctly. It is actually calculated based on values of previous working_days, which means that in the instance this method is fired, the new working_day objects are not yet created and the old ones are still there.
Is there any way to make the self.save! part of the regen_working_days method be fired only after all other transactions are finished? Or am I generally wrong in the way I handle nested objects and the transactions associated with them with my approach?
I've got a model called Brand, on which several things rely including in this example a model called User. If a Brand is deleted then a lot of things will fail. What's the best way to set a default Brand for all its relationships in the event that a Brand is deleted?
I thought writing stuff like this might work:
class User < ActiveRecord::Base
after_save :assign_to_default_brand, :if => :not_branded?
def not_branded?
!self.brand_id?
end
def assign_to_default_brand
self.brand_id = Brand.first
end
end
But it doesn't seem to behave the way I want it to. Is there a best-practice established here? Cheers.
UPDATED
I've thrown a default boolean onto Brand and written this but again it seems to have no effect. Am I missing something?
class Brand < ActiveRecord::Base
after_save :assign_users_to_default
def assign_users_to_default
self.users.all.each { |user| user.brand_id = Brand.where(:default => true).first.id if user.not_branded? }
end
end
It should be a before_save instead of after_save That way the value will be persisted to the database when the instance is saved.
For deletion on a brand you could use after_destroy
class Brand
after_destroy :switch_assigned_users
def switch_assigned_users
User.where(:brand_id => id).update_all(:brand_id => Brand.first)
end
end
This finds all users that assigned to that brand and switches them to the first one.
I'm attempting to increment a counter in my User table from another model.
class Count < ActiveRecord::Base
belongs_to :user
after_create :update_count
def update_count
user = User.find(self.user_id)
user.increment(:count)
end
end
So when count is created the goal would be to increment a counter column for that user. Currently it refuses to get the user after creation and I get a nil error.
I'm using devise for my Users
Is this the right (best practice) place to do it? I had it working in the controllers, but wanted to clean it up.
I'm very inexperienced with Model callbacks.
If User has many Counts and Count belongs to User (like it seems to be), then you might want to use a counter cache. It does exactly what you want to do, and it is built-in into ActiveRecord.
I think a better place for this would be using an observer that listens for the on_create for User objects, and then runs this logic.
Something like:
class UserObserver < ActiveRecord::Observer
def after_create(user)
Counter.find_by_name("user_count").increment
end
end
If you would like more extensible counter caches, check out counter_culture. It supports basic counter cache functionality, but also allows you to create counters of records that meet various conditions. For example, you could easily create an inactive user count with code like this:
class Product < ActiveRecord::Base
belongs_to :category
counter_culture :category, :column_name => \
Proc.new {|model| model.inactive? ? 'inactive_count' : nil }
end
I have two ActiveRecord classes. A simplified view of these classes:
class Account < ActiveRecord::Base
has_many :user_account_roles
end
class UserAccountRole < ActiveRecord::Base
belongs_to :account
# Has a boolean attribute called 'administrator'.
end
What I'm struggling with is that I'd like to be able to apply two validation rules to this:
* Ensuring that the last UserAccountRole cannot be removed.
* Ensuring that the last UserAccountRole that is an administrator cannot be removed.
I'm really struggling to understand the best way of achieving this kind of structural validation. I've tried adding a before_remove callback to the association, but I don't like that this has to throw an error which would need to be caught by the controller. I'd rather this be treated as 'just another validation'.
class Account < ActiveRecord::Base
has_many :user_account_roles, :before_remove => check_remove_role_ok
def check_remove_relationship_ok(relationship)
if self.user_account_relationships.size == 1
errors[:base] << "Cannot remove the last user from this account."
raise RuntimeError, "Cannot remove the last user from this account."
end
end
end
I don't think this makes any difference, but I'm also using accepts_nested_attributes_for.
Why not use a simple validation on Account?
class Account < ActiveRecord::Base
has_many :user_account_roles
validate :at_least_one_user_account_role
validate :at_least_one_administrator_role
private
def at_least_one_user_account_role
if user_account_roles.size < 1
errors.add_to_base('At least one role must be assigned.')
end
end
def at_least_one_administrator_role
if user_account_roles.none?(&:administrator?)
errors.add_to_base('At least one administrator role must be assigned.')
end
end
end
This way nothing needs to be raised, and the record won't be saved unless there's at least one role, and at least one administrator role. Thus when you re-render your edit form on error, this message will show up.
You could place the validation on UserAccountRole. If it is the only UserAccountRole associated with the Account, then it can't be deleted.
An easier solution may be to question an underlying assumption of your design. Why have UserAccountRole be an AR backed model? Why not just make it a plain ruby class? Is the end user going to dynamically define roles? If not, then you could greatly simplify your dilemma by making it a regular ruby class.