I have a problem updating current_user attributes.
Here is my controller:
class BuildingsController < ApplicationController
def create
# ---- many_things
#current_user.user_info.money = 1000
current_user.user_info.update_attributes!(money: 100)
#current_user.user_info.money = 1000 --- why not 100?
# ---- many_things
end
end
And here is my model:
class User < ActiveRecord::Base
has_one :user_info
before_create :initialize_user
def initialize_user
self.create_user_info!(money:1000,level:1)
end
end
UserInfo model is blank:
class UserInfo < ActiveRecord::Base
end
update_attributes doesn't throw any exception, it seems that it doesn't save the record.
Any idea?
Thanks!
Are you trying to set money to $100 or $1,000? If looks like you are setting and resetting the money attribute at the same time.
When you call the code block in create if sets the value to $100. After that, in the model, you have an after callback :after_create that runs initialize_user, which sets the value of money to $1,000. Perhaps you want a before callback on create instead?
Related
So having some troubles how to resolve my puzzle.
I'm having 2 models
1) Mode1.rb
class Model1 < ActiveRecord::Base
set_table_name "Model1"
set_sequence_name "Model1"
module Validate
def validate_discount
errors.add(:discount, "#blank") if discount.blank?
end
end
end
2) Model2.rb
class Model2 < ActiveRecord::Base
include Model1::Validate
validate :validate_discount
end
What i need? The trouble is that on submit page operating model2, so i need to execute validation from there to get proper error display, but as discount exists only in model1 i need to pass it to model2
The error what i get is now:
undefined local variable or method `discount' for #<Model2:0x12c952f8>
Might i need somehow pass it through the controller? I mean smth like this:
Model2.new
Model2["discount"] = 20
Model2.discount
I'm stuck now.
I think you can use attr_accessor for this purpose. With that you can set and get value of discount attribute.
class Model2 < ActiveRecord::Base
include Model1::Validate
attr_accessor :discount
validate :validate_discount
end
With this you can call:
model2 = Model2.new
model2.discount = params[:discount] #or whatever you set value for discount
And then validate it with your Model1::Validate module.
I have a model which have two columns admin_approved and approval_date. Admin update admin_approved by using activeadmin. I want when admin update this column approval_date also update by current_time.
I cant understand how I do this.Which call_back I use.
#app/models/model.rb
class Model < ActiveRecord::Base
before_update 'self.approval_date = Time.now', if: "admin_approved?"
end
This assumes you have admin_approved (bool) and approval_date (datetime) in your table.
The way it works is to use a string to evaluate whether the admin_approved attribute is "true" before update. If it is, it sets the approval_date to the current time.
Use after_save callback inside your model.
It would be something like this:
after_save do
if admin_approved_changed?
self.approval_date = Time.now
save!
end
end
Or change the condition as you like!
You could set the approval_date before your model instance will be saved. So you save a database write process instead of usage of after_save where you save your instance and in the after_save callback you would save it again.
class MyModel < ActiveRecord::Base
before_save :set_approval_date
# ... your model code ...
private
def set_approval_date
if admin_approved_changed?
self.approval_date = Time.now
end
end
end
May be in your controller:
my_instance = MyModel.find(params[:id])
my_instance.admin_approved = true
my_instance.save
In my Rails app I have users who can have many payments.
class User < ActiveRecord::Base
has_many :invoices
has_many :payments
def year_ranges
...
end
def quarter_ranges
...
end
def month_ranges
...
end
def revenue_between(range, kind)
payments.sum_within_range(range, kind)
end
end
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :items
has_many :payments
...
end
class Payment < ActiveRecord::Base
belongs_to :user
belongs_to :invoice
def net_amount
invoice.subtotal * percent_of_invoice_total / 100
end
def taxable_amount
invoice.total_tax * percent_of_invoice_total / 100
end
def gross_amount
invoice.total * percent_of_invoice_total / 100
end
def self.chart_data(ranges, unit)
ranges.map do |r| {
:range => range_label(r, unit),
:gross_revenue => sum_within_range(r, :gross),
:taxable_revenue => sum_within_range(r, :taxable),
:net_revenue => sum_within_range(r, :net) }
end
end
def self.sum_within_range(range, kind)
#sum ||= includes(:invoice => :items)
#sum.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount")
end
end
In my dashboard view I am listing the total payments for the ranges depending on the GET parameter that the user picked. The user can pick either years, quarters, or months.
class DashboardController < ApplicationController
def show
if %w[year quarter month].include?(params[:by])
#unit = params[:by]
else
#unit = 'year'
end
#ranges = #user.send("#{#unit}_ranges")
#paginated_ranges = #ranges.paginate(:page => params[:page], :per_page => 10)
#title = "All your payments"
end
end
The use of the instance variable (#sum) greatly reduced the number of SQL queries here because the database won't get hit for the same queries over and over again.
The problem is, however, that when a user creates, deletes or changes one of his payments, this is not reflected in the #sum instance variable. So how can I reset it? Or is there a better solution to this?
Thanks for any help.
This is incidental to your question, but don't use #select with a block.
What you're doing is selecting all payments, and then filtering the relation as an array. Use Arel to overcome this :
scope :within_range, ->(range){ where date: range }
This will build an SQL BETWEEN statement. Using #sum on the resulting relation will build an SQL SUM() statement, which is probably more efficient than loading all the records.
Instead of storing the association as an instance variable of the Class Payment, store it as an instance variable of a user (I know it sounds confusing, I have tried to explain below)
class User < ActiveRecord::Base
has_many :payments
def revenue_between(range)
#payments_with_invoices ||= payments.includes(:invoice => :items).all
# #payments_with_invoices is an array now so cannot use Payment's class method on it
#payments_with_invoices.select { |x| range.cover? x.date }.sum(&:total)
end
end
When you defined #sum in a class method (class methods are denoted by self.) it became an instance variable of Class Payment. That means you can potentially access it as Payment.sum. So this has nothing to do with a particular user and his/her payments. #sum is now an attribute of the class Payment and Rails would cache it the same way it caches the method definitions of a class.
Once #sum is initialized, it will stay the same, as you noticed, even after user creates new payment or if a different user logs in for that matter! It will change when the app is restarted.
However, if you define #payments_with_invoiceslike I show above, it becomes an attribute of a particular instance of User or in other words instance level instance variable. That means you can potentially access it as some_user.payments_with_invoices. Since an app can have many users these are not persisted in Rails memory across requests. So whenever the user instance changes its attributes are loaded again.
So if the user creates more payments the #payments_with_invoices variable would be refreshed since the user instance is re-initialized.
Maybe you could do it with observers:
# payment.rb
def self.cached_sum(force=false)
if #sum.blank? || force
#sum = includes(:invoice => :items)
end
#sum
end
def self.sum_within_range(range)
#sum = cached_sum
#sum.select { |x| range.cover? x.date }.sum(&total)
end
#payment_observer.rb
class PaymentObserver < ActiveRecord::Observer
# force #sum updating
def after_save(comment)
Payment.cached_sum(true)
end
def after_destroy(comment)
Payment.cached_sum(true)
end
end
You could find more about observers at http://apidock.com/rails/v3.2.13/ActiveRecord/Observer
Well your #sum is basically a cache of the values you need. Like any cache, you need to invalidate it if something happens to the values involved.
You could use after_save or after_create filters to call a function which sets #sum = nil. It may also be useful to also save the range your cache is covering and decide the invalidation by the date of the new or changed payment.
class Payment < ActiveRecord::Base
belongs_to :user
after_save :invalidate_cache
def self.sum_within_range(range)
#cached_range = range
#sum ||= includes(:invoice => :items)
#sum.select { |x| range.cover? x.date }.sum(&total)
end
def self.invalidate_cache
#sum = nil if #cached_range.includes?(payment_date)
end
I need to know if model is in saving state (between before_validate and after_save).
class ModelA < ActiveRecord::Base
before_save: do_before
def do_before
modelb.create(:attr => 123, :ref => self)
end
end
class ModelB < ActiveRecord::Base
before_create: do_before
def do_before
self.ref.my_attr = 321
self.ref.save! unless self.ref.is_saving?
end
end
I need "is_saving?" attribute for every model instance in my project. What is the best way to implement that?
Can't you rather set an instance variable in between :before_save and :after_save by using ActiveRecord's :around_save callback and then yield your save? Anyway, the question is not too clear to me. What is the purpose of is_saving? method?
Clients have many Invoices. Invoices have a number attribute that I want to initialize by incrementing the client's previous invoice number.
For example:
#client = Client.find(1)
#client.last_invoice_number
> 14
#invoice = #client.invoices.build
#invoice.number
> 15
I want to get this functionality into my Invoice model, but I'm not sure how to. Here's what I'm imagining the code to be like:
class Invoice < ActiveRecord::Base
...
def initialize(attributes = {})
client = Client.find(attributes[:client_id])
attributes[:number] = client.last_invoice_number + 1
client.update_attributes(:last_invoice_number => client.last_invoice_number + 1)
end
end
However, attributes[:client_id] isn't set when I call #client.invoices.build.
How and when is the invoice's client_id initialized, and when can I use it to initialize the invoice's number? Can I get this logic into the model, or will I have to put it in the controller?
Generate a migration that adds invoices_number column to users table. Then in Invoice model write this:
class Invoice < ActiveRecord::Base
belongs_to :user, :counter_cache => true
...
end
This will automatically increase invoices_count attribute for user once the invoice is created.
how about this:
class Invoice < ActiveRecord::Base
...
def initialize(attributes = {})
super
self.number = self.client.invoices.size + 1 unless self.client.nil?
end
end
Here is some useful discussion on after_initialize per Jonathan R. Wallace's comment above:
http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html
first of all, you don't need to use the attributes collection, you can just do self.client_id. Better yet, as long as you have a belongs_to :client in your Invoice, you could just do self.client.last_invoice_number. Lastly, you almost always want to raise an exception if an update or create fails, so get used to using update_attributes!, which is a better default choice. (if you have any questions about those points, ask, and I'll go into more detail)
Now that that is out of the way, you ran into a bit of a gotcha with ActiveRecord, initializer methods are almost never the right choice. AR gives you a bunch of methods to hook into whatever point of the lifecycle you need to. These are
after_create
after_destroy
after_save
after_update
after_validation
after_validation_on_create
after_validation_on_update
before_create
before_destroy
before_save
before_update
before_validation
before_validation_on_create
before_validation_on_update
What you probably want is to hook into before_create. Something like this
def before_create
self.number ||= self.client.last_invoice_number + 1 unless self.client
end
What that will do is it will hit up the database for your client, get the last invoice number, increment it by one, and set it as its new number, but only if you haven't already set a number (||= will assign, but only if the left side is nil), and only if you have set a client (or client_id) before the save.