I'm using NumberHelper's number_to_phone method many times in my app. It looks like this...
number_to_phone(phone_number, area_code: true)
But there's never a place where I want the area_code to be false. How should I have it default to true?
One way to do this would be to write your own method that only takes a phone number argument and some options, merges the options with a default value for :area_code, and calls #number_to_phone. You could do this in ApplicationHelper like so:
# application_helper.rb
def num_to_phone(phone_number, opts={})
opts = {area_code: true}.merge(opts)
number_to_phone(phone_number, opts)
end
This way, you can just use your wrapper method without having to worry about trying to monkey patch the original one.
I guess this is what I was looking for.
# application_helper.rb
def formatted_phone(number, options={area_code: true})
number_to_phone(number, options)
end
1) You can set area_code: true through callback method like:-
Class Model
before_create :set_area_code_to_true
private
def set_area_code_to_true
self.area_code = true
end
end
2) You can set default value of area code, when added a new attribute in table through migration like:-
rails g migration add_default_value_to_table
def change
change_column :table_name, :area_code, :boolean, default: true
end
Related
This is the first time I am writing test cases on a rails project which is using RSpec and FactoryGirl
When I run the test case i get the following error
wrong number of arguments (given 0, expected 1)
I have gone through other posts at stack over flow and they are not much helpful in my case.
What I have tried
I am writing a test case on a Model which is called ImportFeed and it looks something like as following
class ImportFeed < ApplicationRecord
belongs_to :staffroom
belongs_to :user, optional: true # We don't have to have a user
validates_presence_of :url, :feed_type
validates :enabled, presence: true, allow_blank: true
def initialize(params)
super(params)
self.enabled = false if self.enabled.blank?
self.default_radius = DEFAULT_RADIUS if self.default_radius.blank?
self.default_days = DAYS_DEFAULT if self.default_days.blank?
end
end
This is what my test case looks like
require 'rails_helper'
describe JobImporters::JoraJobImporter, '.run' do
it 'should create an instance of ImportFeed' do
feed = ImportFeed::new FactoryGirl.create(:import_feed, :import1)
expect(feed).to be_a ImportFeed
end
end
This is the factory
FactoryGirl.define do
factory :import_feed do
trait :import1 do
enabled true
feed_type 'example'
staffroom_id 7526
url Faker::Internet::url
end
end
end
When I run this I get the error mentioned at the beginning of this question,
If I pass the data to the test case without FactoryGirl then my test case works and passes for example if I replace
feed = ImportFeed::new FactoryGirl.create(:import_feed, :import1)
with
feed = ImportFeed::new enabled: true, staffroom_id: 7526, feed_type: 'example', url: Faker::Internet::url
the test case passes.
I will really appreciate if someone can point to me what am I doing wrong here.
Because you're overriding initialize method, so you got unexpected exception.
Don't override initialize on ActiveRecord objects
ActiveRecord::Base doesn't always use new to create objects, so initialize might not be called. [link]
In order to solve your problem, you should set your attributes in callback instead
class ImportFeed < ApplicationRecord
# ...
after_initialize :set_my_attributes
private
def set_my_attributes
self.enabled = false if self.enabled.blank?
self.default_radius = DEFAULT_RADIUS if self.default_radius.blank?
self.default_days = DAYS_DEFAULT if self.default_days.blank?
end
end
One more thing:
You're testing creating an instance of ImportFeed functionality, so you should either pass params to new or create methods to test it, but you pass an instance of ImportFeed to it (from FactoryGirl).
According to the docs, ActiveRecord#new accepts Hash only (the default argument is {} if you don't pass anything).
If you pass an object to it, you'll get ArgumentError exception along with "When assigning attributes, you must pass a hash as an argument" message
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
return if new_attributes.empty?
attributes = new_attributes.stringify_keys
_assign_attributes(sanitize_for_mass_assignment(attributes))
end
I think you are just using the initialize method to change the values according to the conditions:
def initialize(params)
super(params)
self.enabled = false if self.enabled.blank?
self.default_radius = DEFAULT_RADIUS if self.default_radius.blank?
self.default_days = DAYS_DEFAULT if self.default_days.blank?
end
You should not override it (my suggestion) as it may break many things. So instead you may change the values on a callback (before_validation, before_save, before_create, after_initialize whichever suits you) like this:
before_create :set_default_radius, if: proc { |feed| feed.default_radius.blank? }
def set_default_radius
self.default_radius = DEFAULT_RADIUS
end
And the best way to do this is having the default value in database itself. You can define that in migration:
def up
change_column :import_feeds, :default_radius, :integer, default: 0
end
def down
change_column :import_feeds, :default_radius, :integer, default: nil
end
So if the value is not defined it will always set to the default value mentioned in the migration file.
Also you may have a read of several question related to this which has some very good answers and explanation:
How to override "new" method for a rails model
Why is overriding ActiveRecord::Base.initialize wrong?
Overriding ApplicationRecord initialize, bad idea?
I'm newbie in Ruby, so need help, because can not find answer :(
I have Rails application, which has model Event like this:
class Event < ActiveRecord::Base
before_validation :clean_input
....
protected
def clean_input
fields = %w[title preview content]
fields.each do |field|
eval "self.#{field} = ActionController::Base.helpers.sanitize(self.#{field})"
end
end
end
The method purpose is cleaning input from dangerous data before validation and before storing it in DB.
Before I wrote this method it looked like the one below (with lot of duplication, that is not DRY at all). This code is very clear, but when adding new field I'll have to add new line instead of adding new element to an array:
def clean_input
self.title = ActionController::Base.helpers.sanitize(self.title)
self.preview = ActionController::Base.helpers.sanitize(self.preview)
self.content = ActionController::Base.helpers.sanitize(self.content)
end
So my questions are:
1) is it possible to omit eval ... in favor of call or send somehow (all my attemps were useless)?
2) is it possible to declare before_validation :clean_input like this before_validation clean_input: fields: { :title, :preview, :content}?
1) Sure:
def clean_input
%w(title preview content).each do |field|
self.send("#{field}=", ActionController::Base.helpers.sanitize(self.send(field)))
end
end
2) No, and your current implementation is ok
Since you are updating an active record model, there are a few other ways of updating attributes, for example:
def clean_input
%i(title preview content).each do |field|
self[field] = ActionController::Base.helpers.sanitize(self[field])
end
end
I'm trying to make simple view generator and using DRY principle, I don't want to have my own html (erb/haml/slim) templates. I'd like my generator to hook to existing template engine and pass it some arguments.
My view_generator.rb file looks like this:
class ViewGenerator < Rails::Generators::NamedBase
source_root File.expand_path('../templates', __FILE__)
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
def some_custom_method
(...)
end
hook_for :template_engine, :as => :scaffold
end
Everything works fine like this. What I'd like to do in my some_custom_method is to add couple of attributes:
def some_custom_method
new_attribute = Rails::Generators::GeneratedAttribute.new("description")
new_attribute.type = :integer
attributes << new_attribute
end
What happens is that I insert new_attribute in attributes array, but when the hook_for is executed, the attribute variable reverts back to original one passed from command line.
How can I bypass this?
At the point some_custom_method is called, attributes are already set (via ARGV) and by checking the code I don't see a clear way to alter them from there. You can use another approach by overriding start class method in your generator and manipulate the args directly, like this:
class ViewGenerator < Rails::Generators::NamedBase
# your code ...
def self.start(args, config)
args.insert(1, 'description:integer') # 0 being the view name
super
end
end
In plain java I'd use:
public User(String name, String email) {
this.name = name;
this.email = f(email);
this.admin = false;
}
However, I couldn't find a simple standard way to do in rails (3.2.3), with ActiveRecords.
1. override initialize
def initialize(attributes = {}, options = {})
#name = attributes[:name]
#email = f(attributes[:email])
#admin = false
end
but it might be missed when creating a record from the DB
2. using the after_initialize callback
by overriding it:
def after_initialize(attributes = {}, options = {})
...
end
or with the macro:
after_initialize : my_own_little_init
def my_own_little_init(attributes = {}, options = {})
...
end
but there may be some deprecation issues.
There are some other links in SO, but they may be out-of-date.
So, what's the correct/standard method to use?
Your default values should be defined in your Schema when they will apply to ALL records. So
def change
creates_table :posts do |t|
t.boolean :published, default: false
t.string :title
t.text :content
t.references :author
t.timestamps
end
end
Here, every new Post will have false for published. If you want default values at the object level, it's best to use Factory style implementations:
User.build_admin(params)
def self.build_admin(params)
user = User.new(params)
user.admin = true
user
end
According to Rails Guides the best way to do this is with the after_initialize. Because with the initialize we have to declare the super, so it is best to use the callback.
One solution that I like is via scopes:
class User ...
scope :admins, where(admin: true)
Then you can do both: create new User in the admin status(i.e. with admin==true) via User.admins.new(...) and also fetch all your admins in the same way User.admins.
You can make few scopes and use few of them as templates for creating/searching. Also you can use default_scope with the same meaning, but without a name as it is applied by default.
I was searching for something similar this morning. While setting a default value in the database will obviously work, it seems to break with Rails' convention of having data integrity (and therefore default values?) handled by the application.
I stumbled across this post. As you might not want to save the record to the database immediately, I think the best way is to overwrite the initialize method with a call to write_attribute().
def initialize
super
write_attribute(name, "John Doe")
write_attribute(email, f(email))
write_attribute(admin, false)
end
This will work in rails 4.
def initialize(params)
super
params[:name] = params[:name] + "xyz"
write_attribute(:name, params[:name])
write_attribute(:some_other_field, "stuff")
write_attribute(:email, params[:email])
write_attribute(:admin, false)
end
How can I read the value that a controller has set in a before_save callback?
Example:
I have a model with a url field. Before saving, I want to check if the url was changed. If so, do some stuff with both the new and old url.
Is that possible?
Try something like this:
before_save { |m| if m.url_changed? ... }
Also see the docs on ActiveModel::Dirty
better way
before :todo, if: :first_name_or_last_name_changed?
and your todo method
def first_name_or_last_name_changed?
first_name_changed? || last_name_changed?
end
If it has changed it should be in the params hash. If it hasn't changed it shouldn't be in there. Therefore, you can put this custom handling into the controller, or in a method on the model that does this.
If you really want to access it inbefore_save check the documentation on ActiveRecord callbacks, and you will see how to access the before and after values.
I tried this thing but it did not work out
#Non-working code
before_save: record_old_value
after_save: record_change
def record_old_value
#old_value = self.field
end
def record_change
if #old_value==self.field
create_record_in_history :old_value => #old_value, :new_value => self.field
end
end
The reason it didn't work out was because we set self.field=new_value so in before_save it is not accessible. But rails has some more Active-record function like field_changed which can directly be used in both before_save and after_save. So I ended up with this solution
#working code
after_save :run_function
def run_function
#old_value = field_was
if #old_value==self.field
create_record_in_history :old_value => #old_value, :new_value => self.field
end
end