Rails variable returns two different values? - ruby-on-rails

For some weird reason an instance variable I have puts out two different values on two different occasions.
$ puts #project.to_yaml
gives:
id: 3
title: '123'
created_at: 2014-04-07 23:54:18.253262000 Z
updated_at: 2014-04-09 09:20:33.847246000 Z
amount_donated: 50000
and
$ #project.amount_donated
gives:
nil
Explain this one to me because I'm terribly lost.
EDIT
Project model
class Project < ActiveRecord::Base
require 'date'
attr_accessor(:amount_donated)
before_save :convert_params
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end

update_column(:amount_donated, value.to_i) shows that you have a column amount_donated, but attr_accessor :amount_donated shows that you have a virtual attribute. So which one is it?
I'd suggest removing attr_accessor :amount_donated
edit:
The attr_accessor :amount_donated does something like this:
class Project < ActiveRecord::Base
require 'date'
before_save :convert_params
def amound_donated
#amount_donated
end
def amound_donated=(value)
#amount_donated = value
end
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
Thus when you accessed #project.amount_donated you were actually accessing the getter method amount_donated not the column (ActiveRecord getter).
Seems that to_yaml saw the column instead of the ActiveRecord's getter.

Try this, might be you are using cached copy of #project
#project.reload.amount_donated

Related

ruby about attr_accessor, instance variables, local varibles

I'm so confused about that..
like this
class Box
attr_accessor :item ,:item2
def initialize(item2)
#item = []
#item2 = item2
end
def add(product)
item << product
end
def empty?
item.empty?
end
def increment(n=1)
item2 +=1
end
end
cart =Box.new(123)
cart.add(1)
puts cart.empty? #false
puts cart.item #1
in the 'add' and 'empty?' methods
I use local variable 'item' right?
why I can get the value from #items ??
and I try this
cart.item2 = 345
puts cart.item2 #345
puts cart.increment #'increment': undefined method `+' for nil:NilClass (NoMethodError)
now I can't get the value?
please fix my brain thx
First, read this answer, which is the most-upvoted Ruby post in StackOverflow history. It will help you understand attr_accessor and its cousins attr_reader and attr_writer.
Besides that, your code has many problems.
First, you should not name an Array with a singular variable name like item. Use a plural items to make its purpose clear.
Second, the name item2 is not good. For your attribute, use something descriptive like counter, and for the variable passed as an argument to initialize it, let's use something descriptive like initial_count.
Third, your increment method takes an optional argument but then ignores it. Wouldn't it be surprising if someone called box.increment(2) and the attribute was incremented by only 1? The intent of this method is to use counter += n instead of counter += 1.
Fourth, to set counter from within the class, we need to use self. So instead of counter += n, we have to do self.counter += n.
Finally, consider whether you want the attributes to be readable and writable from an outside source, or whether you want to reserve write privileges to the object itself. Because you have methods to add things to items and to increment counter, you probably want to conceal write privileges. I would use attr_reader publicly and attr_writer privately.
Incorporating these suggestions, here's the resulting code:
class Box
attr_reader :counter, :items
def initialize(initial_count)
#counter = initial_count
#items = []
end
def add(product)
items << product
end
def empty?
items.empty?
end
def increment(n = 1)
self.counter += n
end
private
attr_writer :counter, :items
end
Now you can do this, all of which makes sense, more or less:
>> cart = Box.new(123)
>> cart.increment(2)
>> cart.counter
#> 125
>> cart.add('A product')
>> cart.add('Another product')
>> cart.items
#> ["A product", "Another product"]
But if you try to set counter or items directly, you'll get an error:
>> cart.counter = 1
#> NoMethodError: private method `counter=' called for #<Box:0x007fc13e17dc50>

How to format values before saving to database in rails 3

I have a User model with Profit field. Profit field is a DECIMAL (11,0) type. I have a masked input on the form which allows user to input something like $1,000. I want to format that value and remove everything except numbers from it so i will have 1000 saved. Here is what i have so far:
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
end
But it keeps saving 0 in database. Looks like it is converting it to decimal before my formatting function.
Try this:
def profit=(new_profit)
self[:profit] = new_profit.gsub(/[^0-9]/, '')
end
First of all, this:
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
is pretty much the same as this:
def format_values
return if(self.profit.nil?)
p = self.profit
s = p.to_s
s.delete!('^0-9')
end
So there's no reason to expect your format_values method to have any effect whatsoever on self.profit.
You could of course change format_values to assign the processed string to self.profit but that won't help because your cleansing logic is in the wrong place and it will be executed after '$1,000' has been turned into a zero.
When you assign a value to a property, ActiveRecord will apply some type conversions along the way. What happens when you try to convert '$1,000' to a number? You get zero of course. You can watch this happening if you play around in the console:
> a = M.find(id)
> puts a.some_number
11
> a.some_number = 'pancakes'
=> "pancakes"
> puts a.some_number
0
> a.some_number = '$1,000'
=> "1,000"
> puts a.some_number
0
> a.some_number = '1000'
=> "1000"
> puts a.some_number
1000
So, your data cleanup has to take place before the data goes into the model instance because as soon as AR gets its hands on the value, your '$1,000' will become 0 and all is lost. I'd put the logic in the controller, the controller's job is to mediate between the outside world and the models and data formatting and mangling certainly counts as mediation. So you could have something like this in your controller:
def some_controller
fix_numbers_in(:profit)
# assign from params as usual...
end
private
def fix_numbers_in(*which)
which.select { |p| params.has_key?(p) }.each do |p|
params[p] = params[p].gsub(/\D/, '') # Or whatever works for you
end
end
Then everything would be clean before ActiveRecord gets its grubby little hands on your data and makes a mess of things.
You could do similar things by overriding the profit= method in your model but that's really not the model's job.
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit = profit.to_s.gsub(/\D/,'') if profit
end
end
def format_values
self.profit.to_d!
end
I recommend you to write custom setter for this particular instance variable #profit:
class User
attr_accessor :profit
def profit= value
#profit = value.gsub(/\D/,'')
end
end
u = User.new
u.profit = "$1,000"
p u.profit # => "1000"
I would suggest using the rails helper of number with precision. Below is some code.
Generic Example:
number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
Rails code Example:
def profit=(new_profit)
number_with_precision(self[:profit], :precision => 1, :significant => true)
end

Convert User input to integer

So I have a form where users can input a price. I'm trying to make a before_validation that normalizes the data, clipping the $ if the user puts it.
before_validation do
unless self.price.blank? then self.price= self.price.to_s.gsub(/\D/, '').to_i end
end
If user inputs $50 This code is giving me 0. If user inputs 50$ this code gives me 50. I think since the data type is integer that rails is running .to_i prior to my before_validation and clipping everything after the $. This same code works fine if the data type is a string.
Anyone have a solution that will let me keep the integer datatype?
One way is to override the mechanism on the model that sets the price, like this:
def price=(val)
write_attribute :price, val.to_s.gsub(/\D/, '').to_i
end
So when you do #model.price = whatever, it will go to this method instead of the rails default attribute writer. Then you can convert the number and use write_attribute to do the actual writing (you have to do it this way because the standard price= is now this method!).
I like this method best, but for reference another way to do it is in your controller before assigning it to the model. The parameter comes in as a string, but the model is converting that string to a number, so work with the parameter directly. Something like this (just adapt it to your controller code):
def create
#model = Model.new(params[:model])
#model.price = params[:model][:price].gsub(/\D/, '').to_i
#model.save
end
For either solution, remove that before_validation.
I would define a virtual attribute and do my manipulation there allowing you to format and modify both the getter and setter at will:
class Model < ActiveRecord::Base
def foo_price=(price)
self.price = price... #=> Mods to string here
end
def foo_price
"$#{price}"
end
You also might want to note that:
"$50.00".gsub(/\D/, '').to_i #=> 5000
My soluction
colum price type decimal
t.decimal :price, precision: 12, scale: 6
# app/concern/sanitize_fields.rb
module SanitizeFields
extend ActiveSupport::Concern
def clear_decimal(field)
return (field.to_s.gsub(/[^\d]/, '').to_d / 100.to_d) unless field.blank?
end
def clear_integer(field)
field.to_s.strip.gsub(/[^\d]/, '') unless field.blank?
end
# module ClassMethods
# def filter(filtering_params)
# results = self.where(nil)
# filtering_params.each do |key, value|
# results = results.public_send(key, value) if value.present?
# end
# results
# end
#
# #use
# #def index
# # #products = Product.filter(params.slice(:status, :location, :starts_with))
# #end
#
# end
end
#app/controllers/products_controller.rb
include SanitizeFields
params[:product][:price] = clear_decimal(params[:product][:price])

deserialize database object

I have a statistic model
class Statistic < ActiveRecord::Base
serialize :value
end
The model contains a value attribute containing a Goals object. I want to deserialize the goals object
when I do
goals = Statistic.all
goals.each do |goal|
test = goal.value
end
I get an error
value was supposed to be a Goals, but was a String
In the debugger I see that goal.value is of type String and contains the goal data
--- !ruby/object:Goals \ngoals: {}\n\ngoals_against: 1\ngoals_for: 0\nversion: 1\n
When I add serialize :value, Goals I get following error when deserialzing
ActiveRecord::SerializationTypeMismatch in ClubsController#new
value was supposed to be a Goals, but was a String
The Goals class
class Goals
attr_accessor :goals
attr_accessor :goals_for
attr_accessor :goals_against
attr_accessor :goals_own
attr_accessor :penalty_for
attr_accessor :penalty_against
def initialize(goals = nil, goals_against = nil, goals_own = nil, penalty_for = nil, penalty_against = nil)
#version = 1
if goals.nil?
#goals = {}
else
#goals = goals
end
#goals_against = goals_against.to_i
#goals_own = goals_own.to_i unless goals_own.nil?
unless penalty_for.nil?
#penalty_for = penalty_for.to_i
#penalty_against = penalty_against.to_i
end
set_goals_for()
end
private
def set_goals_for
#goals_for = 0
#goals.each_value {|value| #goals_for += value.to_i }
#goals_for += #goals_own unless #goals_own.nil?
end
end
Someone knows how I can make rails know that its an goals object and not a string?
Thanks
Most of my experience with serialization problems comes from Rails 1 era, but I recall two usual reasons of serialization failures:
silently ignored exceptions
class reloading
Looking at the file ./activerecord/lib/active_record/base.rb (tag v3.0.7 from git) I see that there is a 'rescue' clause:
def object_from_yaml(string)
return string unless string.is_a?(String) && string =~ /^---/
YAML::load(string) rescue string
end
You may try to investigate what exception is thrown by YAML::load. I usually change this method into something like this:
begin
YAML::load(string)
rescue => e
Rails.logger.warn "YAML exception (ignored): #{e.inspect}"
string
end
About the class reloading, is your problem also visible in test mode? I was registering my classes in YAML, and noticed that the class definition was gone in each second request, since the class object was recreated, and the registered one was taken away from the class chains. I don't think this is your problem, but I am signalling it anyway - maybe this will be helpful?

Manually set updated_at in Rails

I'm migrating my old blog posts into my new Rails blog, and I want their updated_at attribute to match the corresponding value on my old blog (not the date they were migrated into my new Rails blog).
How can I do this? When I set updated_at manually it gets overridden by the before_save callback.
Note: This question is only valid for Rails < 3.2.11. Newer versions of Rails allow you to manually set timestamps without them being overwritten.
If it's a one time thing you can turn record_timestamps on or off.
ActiveRecord::Base.record_timestamps = false
#set timestamps manually
ActiveRecord::Base.record_timestamps = true
When I ran into this issue with my app, I searched around for a bit and this seemed like it made the most sense to me. It's an initializer that I can call where I need to:
module ActiveRecord
class Base
def update_record_without_timestamping
class << self
def record_timestamps; false; end
end
save!
class << self
def record_timestamps; super ; end
end
end
end
end
As of recent versions of Rails (3.2.11 as per iGELs comment) you can set the updated_at property in code and the change will be honoured when saving.
I assume rails is keeping track of 'dirty' properties that have been manually changed and not overwriting on save.
> note = Note.last
Note Load (1.4ms) SELECT "notes".* FROM "notes" ORDER BY "notes"."id" DESC LIMIT 1
=> #<Note id: 39, content: "A wee note", created_at: "2015-06-09 11:06:01", updated_at: "2015-06-09 11:06:01">
> note.updated_at = 2.years.ago
=> Sun, 07 Jul 2013 21:20:47 UTC +00:00
> note.save
(0.4ms) BEGIN
(0.8ms) UPDATE "notes" SET "updated_at" = '2013-07-07 21:20:47.972990' WHERE "notes"."id" = 39
(0.8ms) COMMIT
=> true
> note
=> #<Note id: 39, content: "A wee note", created_at: "2015-06-09 11:06:01", updated_at: "2013-07-07 21:20:47">
So short answer, workarounds are not needed any longer in recent versions of rails.
I see two ways to accomplish this easily:
touch (Rails >=5)
In Rails 5 you can use the touch method and give a named parameter time like described in the documentation of touch
foo.touch(time: old_timestamp)
update_column (Rails >=4)
If you want it in Rails 4 and lower or want to avoid all callbacks you could use one of the update_column or update_columns methods which bypass all safe or touch callbacks and validations
foo.update_column(updated_at, old_timestamp)
or
foo.update_columns(updated_at: old_timestamp)
I took Andy's answer and modified it to accept blocks:
module ActiveRecord
class Base
def without_timestamping
class << self
def record_timestamps; false; end
end
yield
class << self
remove_method :record_timestamps
end
end
end
end
This is riffing off of Andy Gaskell's answer:
class ActiveRecord::Base
class_inheritable_writer :record_timestamps
def do_without_changing_timestamps
self.class.record_timestamps = false
yield
ensure
self.class.record_timestamps = true
end
end
The solution is to temporarily set ActiveRecord::Base.record_timestamps to false:
ActiveRecord::Base.record_timestamps = false
# Make whatever changes you want to the timestamps here
ActiveRecord::Base.record_timestamps = true
If you want a somewhat more robust solution, you may want to try something like what mrm suggested:
module ActiveRecord
class Base
def self.without_timestamping
timestamping = self.record_timestamps
begin
self.record_timestamps = false
yield
ensure
self.record_timestamps = timestamping
end
end
end
end
Then you can easily make changes to models without their timestamps being automatically updated:
ActiveRecord::Base.without_timestamping do
foo = Foo.first
bar = Bar.first
foo.updated_at = 1.month.ago
bar.updated_at = foo.updated_at + 1.week
foo.save!
bar.save!
end
Or, if you only want to update records from a specific class without timestamping:
module ActiveRecord
class Base
# Don't delete Rail's ActiveRecord::Base#inherited method
self.singleton_class.send(:alias_method, :__inherited__, :inherited)
def self.inherited(subclass)
__inherited__
# Adding class methods to `subclass`
class << subclass
def without_timestamping
# Temporarily override record_timestamps for this class
class << self
def record_timestamps; false; end
end
yield
ensure
class << self
remove_method :record_timestamps
end
end
end
end
end
end
E.g:
Foo.without_timestamping do
foo = Foo.first
bar = Bar.new(foo: foo)
foo.updated_at = 1.month.ago
foo.save! # Timestamps not automatically updated
bar.save! # Timestamps updated normally
end
Or you could use an approach similar to what Venkat D. suggested, which works on a per-instance basis:
module ActiveRecord
class Base
def without_timestamping
class << self
def record_timestamps; false; end
end
yield
ensure
class << self
remove_method :record_timestamps
end
end
end
end
E.g:
foo = Foo.first
foo.without_timestamping do
foo2 = Foo.new(parent: foo)
foo.updated_at = 1.month.ago
foo.save! # Timestamps not automatically updated
foo2.save! # Timestamps updated normally
end

Resources