How to prevent save model changes in a before_save callback - ruby-on-rails-3

So i have to 1) not save changes but instead 2) save the audit with these changes. The second part is achieved by send_for_audit method, but if i return false in it the new Audit instance is not created as well as changes to the Article instance are not saved (there is the simplified code).
class Article < ActiveRecord::Base
before_save :send_for_audit, if: :changed?
has_many :audits, as: :auditable
private
def send_for_audit
audits.destroy_all
Audit.create!(auditable: self, data: changes)
end
end
class Audit < ActiveRecord::Base
attr_accessible :auditable, :auditable_id, :auiditable_type, :data
store :data
belongs_to :auditable, polymorphic: true
def accept
object_class = self.auditable_type.constantize
object = object_class.find_by_id(auditable_id)
data.each do |attr, values|
object.update_column(attr.to_sym, values.last)
end
self.destroy
end
end
I've tried to add an additional before_save callback thinking that the order in which they are triggered will do the trick:
before_save :send_for_audit, if: :changed?
before_save :revert_changes
def send_for_audit
audits.destroy_all
Audit.create!(auditable: self, data: changes)
#need_to_revert = true
end
def revert_changes
if #need_to_revert
#need_to_revert = nil
false
end
end
but anyway i got no Audit instance..
Any thoughts how i could achieve the desired result?

i've figured it out
i just dont use before_save, but
def audited_save!(current_user)
if current_user.superadmin
save!
else
audits.destroy_all
Audit.create!(auditable: self, data: changes)
end
end
and then i use that method in the update controller action

Related

Rails: Setting Model Attributes to Attributes from Another Model

I am a little unsure of how to ask this so I apologize for the clunky explanation.
I have three models, User, Waterusage and Goals
class Goal < ApplicationRecord
belongs_to :user
end
class Waterusage < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable,
has_one :waterusage, :dependent => :destroy
has_one :goals, :dependent => :destroy
end
Waterusage is filled out first by users and then goals. Goals is the exactly same schema as waterusage, but uses a portion of the waterusage form and copies the remaining attributes from waterusage.
class Goal < ApplicationRecord
belongs_to :user
# before_validation :get_from_waterusage
before_validation :calculate_totals
def get_from_waterusage
self.household_size = #waterusage.household_size
self.swimming_pool = #waterusage.swimming_pool
self.bathroom_sink_flow_rate = #waterusage.bathroom_sink_flow_rate
self.low_flow_toilet = #waterusage.low_flow_toilet
self.kitchen_sink_usage = #waterusage.kitchen_sink_usage
self.kitchen_sink_flow_rate = #waterusage.kitchen_sink_flow_rate
self.dishwasher_rate = #waterusage.dishwasher_rate
self.dishwasher_multiplier = #waterusage.dishwasher_multiplier
self.laundry_rate = #waterusage.laundry_rate
self.laundry_multiplier = #waterusage.laundry_multiplier
self.lawn_size = #waterusage.lawn_size
self.carwash_rate = #waterusage.carwash_rate
self.carwash_multiplier = #waterusage.carwash_multiplier
self.miles = #waterusage.miles
self.statewater = #waterusage.statewater
self.percent_statewater = #waterusage.percent_statewater
self.pet_cost = #waterusage.pet_cost
end
...
end
Here is the GoalsController
class GoalsController < ApplicationController
before_action :authenticate_user!
def new
#goal = goal.new
end
def create
##user = User.find(params[:user_id])
#goal = current_user.create_goal(goal_params)
redirect_to goal_result_path
end
def destroy
#user = User.find(params[:user_id])
#goal = #user.goal.find(params[:id])
#goal.destroy
redirect_to user_path(current_user)
end
def show
#goal = goal.find(params[:id])
end
def results
if current_user.goal.get_individual_total > 6000
#temp = 6000
else
#temp = current_user.goal.get_individual_total
end
#goal = current_user.goal
end
private
def goal_params
params.require(:goal).permit(:household_size, :average_shower,
:shower_flow_rate, :bath_rate, :bath_multiplier,
:bathroom_sink_usage,
:bathroom_sink_flow_rate, :mellow, :low_flow_toilet,
:kitchen_sink_usage,
:kitchen_sink_flow_rate, :dishwasher_rate,
:dishwasher_multiplier,
:dishwasher_method, :laundry_rate, :laundry_multiplier,
:laundry_method,
:greywater, :lawn_rate, :lawn_multiplier, :lawn_size,
:xeriscaping,
:swimming_pool, :swimming_months, :carwash_rate,
:carwash_multiplier,
:carwash_method, :miles, :statewater, :percent_statewater,
:shopping,
:paper_recycling, :plastic_recycling, :can_recycling,
:textile_recycling,
:diet, :pet_cost, :individual_total, :household_total,
:home_usage, :outdoor_usage,
:individualDifference, :householdDifference, :vehicle_usage,
:power_usage, :indirect_source_usage,
:individualDifference, :householdDifference)
end
end
I currently have the following error:
NameError in GoalsController#create
undefined local variable or method `current_user' for #
<Goal:0x007fbedde9a590>
It seems to be in the way I am retrieving the info from the waterusage model with
self.household_size = #waterusage.household_size
It there a join I could use?
The waterusage model works BTW.
Thanks
Don't know if it's the best way to do that, but I would use something like this:
In your goals model, you can check if its user have a waterusage already. If it has, you fill the values from that water usage
You can do it using after_initialize callback. In your goal model, would be something like
class Goal < ApplicationRecord
belongs_to :user
after_initialize :set_default_values
def set_default_values
waterusage = self.user.try(:waterusage)
if waterusage
self.attribute1 = waterusage.attribute1
self.attribute2 = waterusage.attribute2
self.attribute3 = waterusage.attribute3
#and it goes on...
end
end
end
so, like this when you do a Goal.new, it will check for a waterusage for that user and initialize those values on your goal. So you don't have to change anything on your controller and even if you do it on console, it will work. Guess it's a better practice to do that using models callbacks. Don't know if it solves your problem, but give it a try. Good luck!
Your error message is:
NameError in GoalsController#create
undefined local variable or methodcurrent_user' for #
Goal:0x007fbedde9a590`
The current_user object is automagically defined inside your controller by the Devise gem you're using. It will not be defined inside your models.
One of your comments includes the following snippet you say you're using from within your Goal model: current_user.waterusage.household_size. That is what your error message is referring to. (Note that this snippet from one of your comments disagrees with the code in your original post. This makes it harder to be certain about what is going wrong here.)

How to rewrite Rails associations without separate queries (looping through them again)

Running into some performance issues with the following code (stripped out irrelevant parts).
This is the CardsController#index code:
def index
cards = cards.paginate(page: index_params[:page], per_page: limit)
# Assign bumped attribute
cards.each do |card|
if current_user
card.bumped = card.bump_by?(current_user)
card.bump = card.get_bump(current_user)
else
card.bumped = false
card.bump = nil
end
end
end
Card.rb:
class Card < ActiveRecord::Base
belongs_to :cardable, polymorphic: true, touch: true
belongs_to :user
has_many :card_comments, autosave: true
has_many :card_bumps
has_many :card_bumpers, through: :card_bumps, class_name: 'User', source: :user
def bump_by?(user)
self.card_bumpers.include? user
end
def get_bump(user)
CardBump.find_by(user_id: user.id, card_id: self.id)
end
end
How can I avoid and optimize the second loop on each card where I do the associations of card.bumped and card.bump ?
Thanks in advance
In the model level, since the method bump_by? equals to bump existence, so
card.bumped = !card.bump.empty?
So the whole includes check in method bump_by? can be avoided, which in turn avoid fetching all associated bumps.
First of all, I would optimize your controller code a little and would update all cards with a single query if current_user is not present:
def index
cards = cards.paginate(page: index_params[:page], per_page: limit)
# Assign bumped attribute
if current_user
cards.each do |card|
card.bump = card.get_bump(current_user)
card.bumped = card.bump_by?(current_user)
end
else
cards.update_all(bump: nil, bumped: false)
end
end
There is also a possibility to optimize model code:
class Card < ActiveRecord::Base
belongs_to :cardable, polymorphic: true, touch: true
belongs_to :user
has_many :card_comments, autosave: true
has_many :card_bumps
has_many :card_bumpers, through: :card_bumps, class_name: 'User', source: :user
def bump_by?(user)
# use #exists? to check whether a record is present in the database.
# This will make a `SELECT 1 as count...` query and,
# therefore, perform a lookup on database level.
# #include? in opposite will load ALL associated items from DB,
# turn them into a ruby objects array and perform a lookup in the
# obtained array which is much slower than simple lookup performed by #exists?
get_bump(user) == user || self.card_bumpers.exists?(user.id)
end
def get_bump(user)
#_bump ||= self.card_bumpers.find_by(user_id: user.id)
end
end
Since Card#get_bump is also looking in card_bumpers association we can memorize its result and later use memorized value in Card#bump_by? without hitting database again. If there is no memorized value then fast check for record existence will be performed by a database.
Notice, that I changed lines order in controller to get benefit of memorizing:
card.bump = card.get_bump(current_user)
card.bumped = card.bump_by?(current_user)

Stub associations

I have method and spec.
class Event
def self.renew_subscription(user)
subscription = user.subscription
result = subscription.renew
user.pay(subscription.plan.price_in_cents) if result
result
end
end
let!(:user) { create :user }
describe ".renew_subscription" do
before do
user.subscription.stub!(:renew).and_return(true)
user.subscription.stub!(:plan).
and_return(Struct.new("SP", :price_in_cents).new(699))
end
context "when have to pay" do
it "pays" do
user.should_receive(:pay)
Event.renew_subscription user
end
end
end
There user belongs_to :subscription and subsription belongs_to :plan
Is there the way to stub subscription.renew and subscription.plan (or subscription.plan.price_in_cents)?
I think it's probably safe for you to do something like this:
Subscription.any_instance.stub(:renew).and_return(true)
plan = mock_model(Plan)
Subscription.any_instance.stub(:plan).and_return(plan)
plan.stub(:price_in_cents).and_return(699)
There are probably other ways of doing it too, but I hope that helps.

Rails 3: Find parent of polymorphic model in controller?

I'm trying to find an elegant (standard) way to pass the parent of a polymorphic model on to the view. For example:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
The following way (find_imageable) works, but it seems "hackish".
#PictureController (updated to include full listing)
class PictureController < ApplicationController
#/employees/:id/picture/new
#/products/:id/picture/new
def new
#picture = imageable.pictures.new
respond_with [imageable, #picture]
end
private
def imageable
#imageable ||= find_imageable
end
def find_imageable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
Is there a better way?
EDIT
I'm doing a new action. The path takes the form of parent_model/:id/picture/new and params include the parent id (employee_id or product_id).
I'm not sure exactly what you're trying to do but if you're trying to find the object that 'owns' the picture you should be able to use the imageable_type field to get the class name. You don't even need a helper method for this, just
def show
#picture = Picture.find(params[:id])
#parent = #picture.imagable
#=> so on and so forth
end
Update
For an index action you could do
def index
#pictures = Picture.includes(:imagable).all
end
That will instantiate all 'imagables' for you.
Update II: The Wrath of Poly
For your new method you could just pass the id to your constructor, but if you want to instantiate the parent you could get it from the url like
def parent
#parent ||= %w(employee product).find {|p| request.path.split('/').include? p }
end
def parent_class
parent.classify.constantize
end
def imageable
#imageable ||= parent_class.find(params["#{parent}_id"])
end
You could of course define a constant in your controller that contained the possible parents and use that instead of listing them in the method explicitly. Using the request path object feels a little more 'Rails-y' to me.
I just ran into this same problem.
The way I 'sort of' solved it is defining a find_parent method in each model with polymorphic associations.
class Polymorphic1 < ActiveRecord::Base
belongs_to :parent1, :polymorphic => true
def find_parent
self.parent1
end
end
class Polymorphic2 < ActiveRecord::Base
belongs_to :parent2, :polymorphic => true
def find_parent
self.parent2
end
end
Unfortunately, I can not think of a better way. Hope this helps a bit for you.
This is the way I did it for multiple nested resources, where the last param is the polymorphic model we are dealing with: (only slightly different from your own)
def find_noteable
#possibilities = []
params.each do |name, value|
if name =~ /(.+)_id$/
#possibilities.push $1.classify.constantize.find(value)
end
end
return #possibilities.last
end
Then in the view, something like this:
<% # Don't think this was needed: #possibilities << picture %>
<%= link_to polymorphic_path(#possibilities.map {|p| p}) do %>
The reason for returning the last of that array is to allow finding the child/poly records in question i.e. #employee.pictures or #product.pictures

Custom return value for new / create ActiveRecord model

So I have a model (Photo), where when I call Photo.new #image => #image / Photo.create :image => #image, I want my model to find an existing photo with the same image hash OR create a new Photo from #image. Assume I can't use Photo.find_or_initialize_by_hash because I have a custom find function which finds close copies of images based on a soft image hash.
My first idea was to do
before_validation :check_duplicates, :on => :create
def check_duplicates
self = self.find_duplicate
end
Unfortunately, I realized you can't just redefine self in a model, so now I think the best approach is doing something along the lines of changing the return value from initialize to the duplicate.
Sort of like this, but it doesn't work (and I've heard horror stories about overriding initialize)
def initialize(*params)
super(*params)
return self.find_duplicate || self
end
From what I gather your model structure looks something like this?
class Photo < ActiveRecord::Base
has_one :image
end
class Image < ActiveRecord::Base
belongs_to :photo
end
If so, you can simply do this:
class Photo < ActiveRecord::Base
has_one :image, :uniq => true
end
Or if :image is just an attribute of Photo your first idea was on track:
class Photo < ActiveRecord::Base
before_create :check_duplicate
private
def check_duplicate
Photo.where(:image => self.image).count == 0 # will be false if Photo is found
end
end
which will cancel the Photo from being created if #check_duplicate returns false (http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html)
Or simply
class Photo < ActiveRecord::Base
validates_uniqueness_of :image
end