I'm trying to remove the commas from a field in a model. I want the user to type a number, i.e. 10,000 and that number should be stored in the database as 10000. I was hoping that I could do some model-side normalization to remove the comma. I don't want to depend on the view or controller to properly format my data.
I tried:
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
no worky.
http://github.com/mdeering/attribute_normalizer looks like a promising solution to this common problem. Here are a few examples from the home page:
# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher
# Using one of our predefined normalizers.
normalize_attribute :price, :with => :currency
# You can also define your normalization block inline.
normalize_attribute :title do |value|
value.is_a?(String) ? value.titleize.strip : value
end
So in your case you might do something like this:
normalize_attribute :title do |value|
value.to_s.gsub(',', '')
end
I think you're doing it right. This test passes:
test "should remove commas from thenumber" do
f = Foo.new(:thenumber => "10,000")
f.save
f = Foo.find(f.id)
assert f.thenumber == "10000"
end
And I used your code.
class Foo < ActiveRecord::Base
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
end
Now, my schema is set up for thenumber to be a string though, not an integer.
Started
.
Finished in 0.049666 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
If you wanted to store this in the db as an integer, then you definitely need to override the setter:
def thenumber=(value)
self['thenumber'] = value.to_s.gsub(',','').to_i
end
If you do it your way, with an integer column, it gets truncated by AR....
>> f.thenumber = "10,000"
=> "10,000"
>> f.thenumber
=> 10
That's a little-known thing with Ruby and integers... it auto-casts by truncating anything that's no longer an integer.
irb(main):004:0> i = "155-brian-hogan".to_i
=> 155
Can be cool for things like
/users/155-brian-hogan
#user = User.find_by_id(params[:id])
But not so cool for what you're doing.
So either change the col to a string and use the filter, or change the setter :)
Good luck!
The problem with doing it that way is that for a while, the non-normalized stuff will exist in the object; if you have code that works on the attributes before stuff gets normalised, then that will be a problem.
You could define a setter:
def thenumber=(value)
# normalise stuff here, call write_attribute
end
Unfortunately I think a lot of the Rails form stuff writes the attributes directly, which is one of the reasons I don't tend to use it.
Or you could normalise the params in the controller before you pass them through.
Does ruby let you interchange between a . and [''] ?
I don't know, I'll try later, but I think you are supposed to use .
self.thenumber = self.thenumber.to_s.gsub(',','')
You should return true from your before_validation method, otherwise if the expression being assigned to self['thenumber'] ends up being nil or false, the data will not be saved, per the Rails documention:
If a before_* callback returns false,
all the later callbacks and the
associated action are cancelled.
Ostensibly, you are trying to normalize here then check the result of the normalization with your Rails validations, which will decide if nil/false/blank are okay or not.
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
return true
end
Related
Say I have enum stage [:one, :two, :three, :four] in a model.
When the controller action next_stage is called (button clicked by the user to send it to the next stage), I want to go incrementally from stage X to Y. What's the easiest way to do this? I currently use a big, gross case statement, but I feel like I can do it better. I'll provide my use-case:
Class MyController
def next_stage
# #my_controller.stage => "two"
#my_controller.stage.value++ unless #my_controller.stage.four?
# #my_controller.stage => "three"
end
end
Honestly, if you're trying to store state that moves in a certain order, you should use a state machine. https://github.com/aasm/aasm supports using enums to store the state. You could do something like this;
aasm :column => :stage, :enum => true do
state :stage1, :initial => true
state :stage2
state :stage3
state :stage4
event :increment_stage do
transitions from: :stage1, to: :stage2
transitions from: :stage2, to: :stage3
transitions from: :stage3, to: :stage4
end
end
it not only cleans up your logic, but the tests will be simpler, and you can do all sorts of callbacks on different events. It's really good for any sort of workflow as well (say moving a post from review to approved etc.)
EDIT: Can I also suggest that if you don't want to use a state machine then you at least move this state shifting logic into your model? Controllers are about access control, models are about state.
Sort of hackish but the only way to get an integer out of an enum that I have found is doing
model.read_attribute('foo')
So you could try to do
def next_stage
#my_controller.update_column(:stage, #my_controller.read_attribute('stage')+1) unless #my_controller.four?
end
Your code seems a bit strange, what is #my_controller? What is stage?
Besides that, if I understand correctly, what you want is this:
#my_controller[:stage] += 1 unless #my_controller.four?
Enumerations are stored as integers in the database, and they start at 0, with increments of 1. So, simply access the raw attribute data and increment it.
There exists a gem simple_enum which lets you use more powerful enums.
The enum will let you use both integers and/or symbols, then you can do something like
#my_controller.stage.value_cd += 1 unless #my_controller.stage.last_stage?
You can use it with ActiveRecord/Mongoid, but also any other object.
For an ORM like Mongoid, your model could look like this
class Project
as_enum :status, {
idea: 0,
need_estimate: 1,
in_progress: 2,
finished: 3,
paid: 4,
field: { type: Integer, default: 0 }
def next_step!
self.status_cd += 1 unless self.paid?
end
end
project = Project.new
project.status # => :idea
project.need_estimate? #=> false
project.next_step!
project.need_estimate? #=> true
I don't know whether I get this right. Maybe you can try this:
Class MyController
def next_stage
#my_controller.stage.increment! :value rescue nil
end
end
NEW ANSWER
Ok, I think I better understand what you are looking for. How about this:
In your model set up your Enum with a hash rather than an array:
class MyClass < ActiveRecord::Base
enum stage: {one: 1, two: 2, three: 3, four: 4} #just makes more sense when talking about stages
end
Then you can use the current status' index:
Class MyController
def next_stage
# #my_controller.stage => "two"
current_stage = MyClass.stages[#my_controller.stage] # returns => 1
current_stage += 1 unless current_stage == 4 # returns => 2
#my_controller.update! stage: current_stage
# #my_controller.stage => "three"
end
end
If I understand the docs correctly, this may also work:
Class MyController
def next_stage
# #my_controller.stage => "two"
#my_controller.update! stage: MyClass.stages[#my_controller.stage] + 1 unless #my_controller.stage == :four
# #my_controller.stage => "three"
end
end
this is off the cuff and could probably be cleaned up in some ways. I can't experiment very much because I don't have something setup in a rails app with an enum to mess around with.
old answer left for archival purposes.
def next_stage
self.next
end
edit I saw enums and thought you were shortening enumerable to enum (as in x.to_enum). Not so sure this won't work for you in some form. You asked for something other than an ugly case statement. You could use an enumerator that takes the current enum from that column to set the starting point, and the end point is 4 (or :four depending on how you write it) and have the rescue at the end of the enumerator return your max value.
I'm wondering if its possible to have active record hold off on validating records for a short time? For example, in my webapp I have a table called Sizes that contain an attribute called sort_order, users are able to reorganize the order in which sizes are displayed by modifying the sort_order. When they do that I have this function which will go through the list of sizes and recalculate the sort order, updating the column accordingly.
def update_size_order
#size_ids = params[:categories]
n = 0
ActiveRecord::Base.transaction do
#size_ids.each do |temp|
temp = temp.split('_')
id = temp[1]
size = Size.find(id)
size.sort_order = n
n += 1
size.save!
end
end
render :json => {}
end
The problem arises because in my model I have
validates :sort_order, presence: true, uniqueness: true
but when the model tries to save size I get an error because size isn't necessarily unique. Is there a way I can have ActiveRecord not execute validations until this function has finished? My googling skills have met their match so it seems, but I feel like there is a simple workaround for this problem. Thank you for your help!
Use size.update_attribute :sort_order, n instead of save!. It will simply update the database without running any form of validation. Since you're running this in a transaction, you should be OK, but generally you should avoid update_attribute for the very reason that it bypasses validation and callbacks.
Also, each_with_index is your friend:
def update_size_order
#size_ids = params[:categories]
ActiveRecord::Base.transaction do
#size_ids.each_with_index do |temp,n|
temp = temp.split('_')
id = temp[1]
Size.find(id).update_attribute :sort_order, n
end
end
render :json => {}
end
Since you're only interested in updating the attribute, you can also skip the entire process of instantiating an AR object, and drop your Size.find(id) call in favor of using update_all. This will silently ignore any IDs that aren't found, but it will drastically improve the performance of your loop as you're halving the number of queries being run. Instead of a select query, followed by fully instantiating an AR model, and an update for every single record, it will generate only the update query:
#size_ids.each_with_index do |temp,n|
temp = temp.split('_')
id = temp[1]
Size.where(id: tmp[1]).update_all(sort_order: n)
end
Let's say you put this :
validates: :sort_order,
presence: true,
uniqueness: true,
on: :create
Is that what you wanted ?
In this way, your validation execute only on creating new object .
This is the stripped down version of my model .
model Paper
PAPER_STARTING_NUMBER = 1
validate_uniqueness_of :number, :allow_blank => true
before_create :alocate_paper_number
def alocate_paper_number
return true if self.number.present?
p_number = Paper.maximum('number') || Paper::PAPER_STARTING_NUMBER
self.number = p_number >= Paper::PAPER_STARTING_NUMBER ? p_number+1 : Paper::PAPER_STARTING_NUMBER
return true
end
end
the problem is I have duplicates in the number column .
Any ideas why and how I can fix this without changing the callback .
I know I could add a uniqueness validation on the database or make a sequence on that column , any other ideas ?
First you have to understand the order of callbacks :
(-) save
(-) valid
(1) before_validation
(-) validate
(2) after_validation
(3) before_save
(4) before_create
(-) create
(5) after_create
(6) after_save
(7) after_commit
So as you can see , it validates the uniquity of your number attribute, and then before_create can at its own disposal go against what your validation wants to accomplish.
In regards to a more cleaner architecture, I would put both of these ideas together in your custom model, as it doesn't seem that the number can be choosen by the User. It's just an incrementer, right?
def alocate_paper_number
p_number = Paper.maximum('number') || Paper::PAPER_STARTING_NUMBER
self.number = p_number + 1
end
That snippet alone, would prevent duplicates, as it always increments upwards ( unless, there's the possibility of the number going the other way that I'm not aware of ), and also there's no reason to return all those trues. Its true enough!
It is in de docs. validate_uniqueness_of TRIES to make it unique. But if two processes add one record at the same time, they both can contain the same number.
If you want to guarantee uniqueness, let the database do it. But because that is different for each DB, Rails does not support it by design.
It's explained here: http://guides.rubyonrails.org/active_record_validations_callbacks.html#uniqueness
With the solution: "To avoid that, you must create a unique index in your database."
How I fixed it ( bare in mind that I couldn't return a validation error )
I've added a uniquness index on the number column ( as mu and Hugo suggested )
and because I couldn't return a validation error in the controller
class PaperController < ApplicationController
def create
begin
#paper.save
rescue ActiveRecord::RecordNotUnique
#paper.number = nil
create
end
end
end
I am learning rails and going back to ruby to understand how methods in rails (and ruby really work). When I see method calls like:
validates :first_name, :presence => true
I get confused. How do you write methods in ruby that accept symbols or hashes. The source code for the validates method is confusing too. Could someone please simplify this topic of using symbols as arguments in ruby class and instance methods for me?
UPDATE:
Good one #Dave! But What I was trying out was something like:
def full_name (:first_name, :last_name)
#first_name = :first_name
#last_name = :last_name
p "#{#first_name} #{last_name}"
end
full_name("Breta", "Von Sustern")
Which obviously raises errors. I am trying to understand: Why is passing symbols like this as arguments wrong if symbols are just like any other value?
Symbols and hashes are values like any other, and can be passed like any other value type.
Recall that ActiveRecord models accept a hash as an argument; it ends up being similar to this (it's not this simple, but it's the same idea in the end):
class User
attr_accessor :fname, :lname
def initialize(args)
#fname = args[:fname] if args[:fname]
#lname = args[:lname] if args[:lname]
end
end
u = User.new(:fname => 'Joe', :lname => 'Hacker')
This takes advantage of not having to put the hash in curly-brackets {} unless you need to disambiguate parameters (and there's a block parsing issue as well when you skip the parens).
Similarly:
class TestItOut
attr_accessor :field_name, :validations
def initialize(field_name, validations)
#field_name = field_name
#validations = validations
end
def show_validations
puts "Validating field '#{field_name}' with:"
validations.each do |type, args|
puts " validator '#{type}' with args '#{args}'"
end
end
end
t = TestItOut.new(:name, presence: true, length: { min: 2, max: 10 })
t.show_validations
This outputs:
Validating field 'name' with:
validator 'presence' with args 'true'
validator 'length' with args '{min: 2, max: 10}'
From there you can start to see how things like this work.
I thought I'd add an update for Ruby 2+ since this is the first result I found for 'symbols as arguments'.
Since Ruby 2.0.0 you can also use symbols when defining a method. When calling the method these symbols will then act almost the same as named optional parameters in other languages. See example below:
def variable_symbol_method(arg, arg_two: "two", arg_three: "three")
[arg, arg_two, arg_three]
end
result = variable_symbol_method :custom_symbol, arg_three: "Modified symbol arg"
# result is now equal to:
[:custom_symbol, "two", "Modified symbol arg"]
As shown in the example, we omit arg_two: when calling the method and in the method body we can still access it as variable arg_two. Also note that the variable arg_three is indeed altered by the function call.
In Ruby, if you call a method with a bunch of name => value pairs at the end of the argument list, these get automatically wrapped in a Hash and passed to your method as the last argument:
def foo(kwargs)
p kwargs
end
>> foo(:abc=>"def", 123=>456)
{:abc=>"def", 123=>456}
>> foo("cabbage")
"cabbage"
>> foo(:fluff)
:fluff
There's nothing "special" about how you write the method, it's how you call it. It would be perfectly legal to just pass a regular Hash object as the kwargs parameter. This syntactic shortcut is used to implement named parameters in an API.
A Ruby symbol is just a value as any other, so in your example, :first_name is just a regular positional argument. :presence is a symbol used as a Hash key – any type can be used as a Hash key, but symbols are a common choice because they're immutable values.
I think all replies have missed the point of question; and the fact it is asked by someone who is - I guess - not clear on what a symbol is ?
As a newcomer to Ruby I had similar confusions and to me an answer like following would have made more sense
Method Arguments are local variables populated by passed in values.
You cant use symbols as Arguments by themselves, as you cant change value of a symbol.
Symbols are not limited to hashes. They are identifiers, without the extra storage space of a string. It's just a way to say "this is ...."
A possible function definition for the validates call could be (just to simplify, I don't know off the top of my head what it really is):
def validates(column, options)
puts column.to_s
if options[:presence]
puts "Found a presence option"
end
end
Notice how the first symbol is a parameter all of its own, and the rest is the hash.
I want to implement a method that checks if a model's instance has only nil or empty attributes, except from its id or timestamps.
I've made use of an auxiliary method that removes a key from Hash and return the remaining hash ( question 6227600)
class ActiveRecord::Base
def blank?
self.attributes.remove("id","created_at","updated_at").reject{|attr| self[attr].blank?}.empty?
end
end
I guess that there may be much simpler, efficient or safer way to do this. Any suggestion?
def blank?
self.attributes.all?{|k,v| v.blank? || %w(id created_at updated_at).include?(k)}
end
My response is almost the same that tadman gave, but expressed in a more concise way.
Be careful with two situations:
- **blank?** is not a good choice as name, since if you call **object_a.object_b.blank?** trying to know if there is or not a object_b inside object_a, you'll get true event if the object exists. **empty?** seems a better name
- If databases sets defaults values, it can be tricky.
EDIT: Since build an array every iteration is slow (thanks tadman), a beter solution is:
def empty?
ignored_attrs = {'id' => 1, 'created_at' => 1, 'updated_at' => 1}
self.attributes.all?{|k,v| v.blank? || ignored_attrs[k]}
end
You could just check that all the properties in the attributes hash are not present, or the converse:
class ActiveRecord::Base
def blank?
!self.attributes.find do |key, value|
case (key)
when 'id', 'created_at', 'updated_at'
false
else
value.present?
end
end
end
end
Unfortunately this will not account for things that are set with a default in your database, if any relationship keys are assigned, among other things. You will have to add those as exceptions, or compare the values to a known default state of some sort.
This sort of thing is probably best implemented on a case by case basis.