Rails callback after_save not setting attribute - activerecord

I'm dealing with a problem on a after_save callback. I'm sure there is a easy solution, but I can't figure it out.
I have 3 models: User, Product, Bid. The Product table contains a boolean field "available", which is set default to true. If a User places a bid, the available field should be set to false.
I thought this should work with a callback on the bid model.
I can access and set the available field in the console by typing:
b = Bid.last
b.product.available = false
=> false
However I can't change it via the controller, so I think it doesn't execute the callback. What am I doing wrong?
Thank you all for your help!
product.rb
class Product < ActiveRecord::Base
has_one :bid
belongs_to :user
end
bid.rb
class Bid < ActiveRecord::Base
attr_accessible :product_id, :user_id, :product
belongs_to :product
belongs_to :user
after_save :set_product_status
def set_product_status
self.product.available = false
end
end
bids_controller.rb
...
def create
#user = current_user
product = Product.find(params[:product_id])
#bid = #user.bids.build(product: product)
respond_to do |format|
if #bid.save
...

Since bid belongs_to product, you should save the product too.
def set_product_status
self.product.available = false
self.product.save
end

Related

Attempting to create a database item using the has_one relationship, no exceptions, but still no item

Models:
A User has_one Ucellar
A Ucellar belongs_to User
I have confirmed from multiple sources that these are set up correctly. For posterity, here is the top portion of those two models.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
This is actually the entire Ucellar model.
class Ucellar < ActiveRecord::Base
belongs_to :user
end
Ucellar has a column called user_id, which I know is necessary. The part of my application that creates a user uses the method create_with_oath. Below is the entire User class. Note the second line of the create method.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
def create
#user = User.new(user_params)
#ucellar = #user.create_ucellar
end
def add_provider(auth_hash)
# Check if the provider already exists, so we don't add it twice unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
end
end
def self.create_with_omniauth(auth)
user = User.create({:name => auth["info"]["name"], :email => auth["info"]["email"]})
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
EDIT:
Forgot to summarize the symptoms. On create, the user is in the db, with no exceptions thrown, and nothing to signify that anything went wrong. However, the related ucellar is never created. Per the documentation Here, the create method should create AND save the related ucellar.
It should create ucellar too.
Try to get the error messages after the creation by calling:
raise #user.errors.full_messages.to_sentence.inspect
I'm not sure why this wasn't working, but I ended up just moving this code out of the create action of the user controller, and putting it directly after an action that was creating a user. It solved my issue though. Thanks everyone for your help!

Update on has_many should update attribute on the parent class

I have a User model which has_many :scores.
If I add a :score to the user, the user should do a recalculation of the playcounter:
class User < ActiveRecord::Base
has_many :scores, inverse_of: :user
accepts_nested_attributes_for :scores
attr_accessible :level
before_save: set_levels
def set_levels
self.level = calculate_level
end
def calculate_level
self.scores.count
end
end
When I add a score via User.score.create(:time => 10) the score get's saved but the level does not get updated.
How can I rerun the set_levels if a child was attached?
(it's not a countercache column, method it's more complex, just sketched here)
many thanks
You need to add a callback to the Score model, perhaps an after_commit on the score model to tell the parent User to recalculate the level.
class Score < ActiveRecord::Base
after_commit do
# can just call touch, as before_save will calc
user.touch
end
end
Now, that is only if you want to recalculate on every score, and synchronously.
More often I will drop a message, such as to resque/sidekiq, to do this, so my score create/updates are fast.
class Score < ActiveRecord::Base
after_commit do
UpdateUserLevelWorker.perform_async(user_id)
end
end
class UpdateUserLevelWorker
include Sidekiq::Worker
def perform(user_id)
# can just call touch, as before_save will calc
User.find(user_id).touch
end
end
I've got it working with
after_create :set_level
def set_level
self.update_attribute :level, calculate_level
end

Rails 3. Decide on save if the object should be saved or not

iam just asking myself, whats the best solution for my problem.
Here are my models:
class Product < ActiveRecord::Base
has_many :prices, :class_name => "ProductPrice"
accepts_nested_attributes_for :prices
end
class ProductPrice < ActiveRecord::Base
belongs_to :product
end
The controller
def create
#product = Product.new(params[:product])
#product.save
...
end
What i want to do is to prevent all ProductPrices from being saved when product_price.value == nil or product_price.value == 0.0
before_save hook in ProductPrice. return false will rollback the whole transaction, thats not what i want to do. i just want to "kick" all prices with value == 0 or value == nil
first kick all price_params from params[...] and than call Product.new(params[:product]) seems not to be the rails way eighter...
after Product.new(params[:product]) iterate over all prices and delete them from the array. but the logic should be in my models right? i just dont want to repeat myself on every controller that creates new prices...
can someone tell me the best solution for that? whats the rails way?
thanks!
What you want it called a validation hook, something like this:
class ProductPrice < ActiveRecord::Base
belongs_to :product
validates :value, :numericality => {:greater_than => 0.0 }
end
See http://guides.rubyonrails.org/active_record_validations_callbacks.html for other ways you may want to do this with finer control.
To avoid adding these invalid prices in the first place, you can remove them from the nested attributes hash like this:
class Product < ActiveRecord::Base
def self.clean_attributes!(product_params)
product_prices = product_params['prices'] || []
product_prices.reject!{|price| price['value'].to_f == 0 rescue true }
end
end
Product.clean_attributes!(params[:product])
Product.new(params[:product])

Rails get related items through two different relationships

I have a "two middleman" model setup as shown below:
User
has_many :comments
has_many :ratings
Comment
belongs_to :user
belongs_to :movie
Rating
belongs_to :user
belongs_to :movie
Movie
has_many :comments
has_many :ratings
Whats the best way to get all Movies that a User is associated with (either commented on or rated)?
I'd like to be able to call User.get_movies(user_id) and get back an ActiveRecord::Relation object so that it's chainable (i.e. User.get_movies(user_id).limit(3).order(...)). This returns a regular old array, and I suspect I'm hitting the database way more than I need to be.
def self.get_movies(user_id)
user = self.where(:id => user_id).includes({:comments => :movie}, {:ratings => :movie})
movies = []
user.comments.each do |comment|
movies.push(comment.movie)
end
user.ratings.each do |rating|
movies.push(rating.movie)
end
movies.uniq!
end
def movies
Movie.includes(:ratings, :comments).where("`ratings`.user_id = ? OR `comments`.user_id = ?", self.id, self.id)
end
Untested, but I'm pretty sure using a joins instead of includes also works.

ActiveRecord :through to set default values on through table

I would like to set a default value in a has_many through association.
Lets say I have three models:
People
Friends
Dogs
A person can request that a dog becomes their friend.
So a person would create an association where friends has an active column = false.
User
has_many :friends
has_many :dogs, :through => :friends
Now when I assign a dog to a user
User.find(1).dogs << dog
The friends table has null in the active column.
My friends model is defined as
Friend
def initialize(args = {})
super(args)
active = false
end
yet this does not work because the friend object is never created. Do I have to manually create one?
To set default values of a model; In the model I do this
before_save :default_values
private
def default_values
self.status = :active unless self.status
end
Not sure if this is the correct approach though.
With the following code you'll create a new friend with active = false
class User < ActiveRecord::Base
has_many :friends, :conditions => "active = false"
has_many :dogs, :through => :friends
end
#user = User.new
#user.friends.create #or #user.friends.build

Resources