Rails 3 - Update Parent Class - ruby-on-rails-3

I have a Trans (transaction) class in my application that submits a transaction to the server. When a transaction is submitted, I also want to do an update on a parent User class to update their cached "balance." Here is relevant code:
# tran.rb
class Tran < ActiveRecord::Base
belongs_to :submitting_user, :class_name => 'User'
end
And my controller:
#trans_controller.rb
def create
#title = "Create Transaction"
# Add the transaction from the client
#tran = Tran.new(params[:tran])
# Update the current user
#tran.submitting_user_id = current_user.id
# ERROR: This line is not persisted
#tran.submitting_user.current_balance = 4;
# Save the transaction
if #tran.save
flash[:success] = 'Transaction was successfully created.'
redirect_to trans_path
I have a couple of problems:
When I update the current_balance field on the user, that balance isn't persisted on the user after the transaction is saved. I think maybe I need to use update_attributes?
I am not even sure that the code should be a part of my controller - maybe it makes more sense in the before_save of my model?
Will either of these make this transactional?

def create
title = "Create Transaction"
#tran = Tran.new(params[:tran])
#tran.submitting_user_id = current_user.id
# to make it "transactional" you should put it after #tran.save
if #tran.save
current_user.update_attribute :current_balance, 4
...
And yes - it is better to put it into after_save callback
class Tran < AR::Base
after_save :update_user_balance
private
def update_user_balance
submitting_user.update_attribute :current_balance, 4
end
end

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.)

Rails saving data from associated form

How can I get the data from an associated form and insert it to the associated table from the main model?
class Supplier < ActiveRecord::Base
has_one :account, foreign_key: "acc_sup_id", :autosave => true
self.primary_key = 'sup_id'
end
class Account < ActiveRecord::Base
belongs_to :supplier, foreign_key: "acc_sup_id"
self.primary_key = 'acc_id'
self.table_name = 'accounts'
end
I am having a combined form for Supplier and Account. When I submit I need to find a way to insert the corresponding values to Supplier and Account. The problem is Supplier values is inserting properly but not Account.
I have asked the same question in several forums, groups and even in stack but nobody seems to give a convincing answer.
The basic strategy is to first look at what params are being submitted when the form is submitted. You could add a line in the controller action such as raise params.inspect to see that. Make sure that those paras contain all the data you need; if not then there is some problem in the view that generates that form.
Once you have all the data getting to the controller action, then you need to change the controller action so that is properly interprets all the data and puts it into the correct models.
I cannot give any more specific advice unless you show the code for your view, the result from doing params.inspect, and the code for the controller action that takes the data.
Try this.
Let's assume that there are orders and customers tables and that you want to perform CRUD operations on customers from orders form.
Customer model is very simple
class Customer < ActiveRecord::Base
attr_accessible :name
end
Order model must provide virtual attributes for all customer's attributes (attr_accessor construct). CRUD for customers is provided through callbacks. Validations can be used as well.
class Order < ActiveRecord::Base
attr_accessor :customer_name
attr_accessible :description, :number, :customer_name
belongs_to :customer
validates_presence_of :number
validates_presence_of :description
validates_presence_of :customer_name
before_save :save_customer
after_find :find_customer
after_destroy :destroy_customer
protected
def save_customer
if self.customer
self.customer.name = self.customer_name
else
self.customer = Customer.create(name: self.customer_name)
end
self.customer.save
end
def find_customer
self.customer_name = self.customer.name
end
def destroy_customer
self.customer.destroy
end
end
Example grid for Order model.
class Orders < Netzke::Basepack::Grid
def configure(c)
super
c.model = 'Order'
c.items = [
:description,
:number,
:customer_name
]
c.enable_edit_inline = false
c.enable_add_inline = false
end
def preconfigure_record_window(c)
super
c.form_config.klass = OrderForm
end
end
Example form for Order model.
class OrderForm< Netzke::Basepack::Form
def configure(c)
super
c.model = 'Order'
c.items = [
:description,
:number,
:customer_name
]
end
end

Rails ActiveRecord define database objects within other object

I'm relatively new to rails and very new to database manipulation.
I'm trying to create a class within the database which contains a number of custom objects within it. These custom objects are also to be stored in the database in a separate table. I've managed to set this up as follows
class MyClass < ActiveRecord::Base
has_many :other_objects, :dependent => destroy
end
class OtherObject < ActiveRecord::Base
belongs_to :my_class
attr_accessible :some_stuff...
end
I've created the appropriate database tables and managed to get it working.
Now what I want to do is have (four) particular instances of "OtherObject"s in my class, which can be accessed by some straightforward identifier, something like
test = MyClass.new
...
test.instance_of_other_object.some_attribute = "blahblah"
Such that this updates the database entry for the associated object. What is the best way to go about this?
That has_many association sets up MyClass#other_objects (and a bunch of other methods) to allow you to easily work with associated records.
You probably want:
my_class.other_objects.each do |other_object|
other_object.update_attributes(:foo => 'bar')
end
If you want a direct SQL update, you can use update_all:
my_class.other_objects.update_all(:foo => 'bar')
Update:
If that's the sort of association you need, you may define a belongs_to association:
class MyClass < ActiveRecord::Base
has_many :other_objects, :dependent => :destroy
# uses :selected_other_object_id
belongs_to :selected_other_object, :class_name => "OtherObject"
end
my_class = MyClass.first
my_class.selected_other_object = other_object # Set the object.
# => #<OtherClass:...>
my_class.selected_other_object_id # ID has been set.
# => 10
my_class.selected_other_object # Retrieve the object.
# => #<OtherClass:...>
my_class.selected_other_object.save # Persist ID and other fields in the DB.
my_class = MyClass.find(my_class.id) # If you fetch the object again...
# => #<MyClass:...>
my_class.selected_other_object_id # The ID is still there.
# => 10
my_class.selected_other_object # You have the ID, you get the object.
# => #<OtherClass:...>
my_class.selected_other_object.foo = "bar" # Access associated object this way.
another_variable = my_class.selected_other_object # Or this way.
Remember however that this does not assume that :selected_other_object is a subset of :other_objects.
Also note that the selected_other_object and selected_other_object= methods are already set up when you set up the association, so you don't have to define these yourself.
This is not a complete answer but I have come up with something that works for getting the object, but not for setting it.
class MyClass < ActiveRecord::Base
has_many :other_objects, :dependent => destroy
def particular_instance
return OtherObject.find_by_id(self.particular_instance_id)
end
end
Where my db schema looks like this
create_table "my_classs", :force => true do |t|
t.integer "particular_instance_id"
end
create_table "other_objects", :force => true do |t|
t.integer "my_class_id"
t.string "some_attribute"
end
Update
In order to set an attribute of the other_object class the update_attributes method can be used
my_class.particular_instance.update_attributes(:some_attribute => "blah")
Use object.dup now for rails 3.1 and on.
a = MyClass.first # finds the first instance of the model "MyClass"
b = a.dup # duplicates the record.
c = a.dup # duplicates the record again.
b.field_name = "blahblah"
c.fielf_name = "blahblah"
b.save # saves the record into the database for object b.
c.save # saves the record into the database for object c.
If you look into your database, you can see that a new record has been created. It is identical to the first record excepted that it has a new id.
Also check Duplicate a model for more information about it.

How to prevent save model changes in a before_save callback

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

Ensuring that at least one related record still exists

Is there a better way to ensure that I don't delete the last record of a relation? I feel like this should be done through a validation, but could not make that stop the destroy action.
FYI - #organization is present because nested routes
class LocationsController < ApplicationController
....
....
def destroy
#organization = Organization.find(params[:organization_id])
#location = #organization.locations.find(params[:id])
count = Location.find_all_by_organization_id(#location.organization_id).count
if count > 1
#location.destroy
flash[:notice] = "Successfully destroyed location."
redirect_to #organization
else
flash[:notice] = "Could not destroy the only location."
redirect_to #organization
end
end
end
You might also consider the before_destroy callback (though I don't think your version is all that bad):
http://edgeguides.rubyonrails.org/active_record_validations_callbacks.html#destroying-an-object