Rails 4.1
Ruby 2.0
Windows 8.1
I have a three different models in my application where I need to "sanitize" the phone numbers and email prior to saving. Can I do something like this in each of the models:
before_save :sanitize_phones_and_email
and in helpers/application_helper.rb:
def sanitize_phones_and_email
(self.email = email.downcase) if attribute_present?("email")
(self.work_phone = phony_normalize work_phone, :default_country_code => 'US') if attribute_present?("work_phone")
(self.mobile_phone = phony_normalize mobile_phone, :default_country_code => 'US') if attribute_present?("mobile_phone")
(self.fax_phone = phony_normalize fax_phone, :default_country_code => 'US') if attribute_present?("fax_phone")
(self.other_phone = phony_normalize other_phone, :default_country_code => 'US') if attribute_present?("other_phone")
end
Would "self" be processed properly by Rails? (since I can't pass it as an argument to a method)
Helpers should only be used for methods that will be used in your views.
To answer your question, no, this will not work. You cannot use view helpers within your models.
If "sanitize_phones_and_email" were defined in every model where you are using it, it would work just fine (and "self" would refer to the instance of that model).
If you are interested in DRY'ing out your models, a simple and effective way (but not necessarily the best object oriented practice) is to use a mixin. When you include a mixin, the methods in that module automatically become instance methods on the class. "self" will refer to the object that it was included in.
For example, in Rails 4 you could put something like this in your "concerns" folder:
app/models/concerns/sanitzable.rb:
module Sanitizable
extend ActiveSupport::Concern
included do
before_save :sanitize_phones_and_email
end
def sanitize_phones_and_email
(self.email = email.downcase) if attribute_present?("email")
(self.work_phone = phony_normalize work_phone, :default_country_code => 'US') if attribute_present?("work_phone")
(self.mobile_phone = phony_normalize mobile_phone, :default_country_code => 'US') if attribute_present?("mobile_phone")
(self.fax_phone = phony_normalize fax_phone, :default_country_code => 'US') if attribute_present?("fax_phone")
(self.other_phone = phony_normalize other_phone, :default_country_code => 'US') if attribute_present?("other_phone")
end
end
app/models/my_model.rb
class MyModel < ActiveRecord::Base
include Sanitizable
end
Related
How does one go about partial updates (i.e. via PATCH)? rake routes indicates that def update handles PUT and PATCH. This is how my Rails API is setup:
#user.first_name = user_params[:attributes][:'first-name']
#user.last_name = user_params[:attributes][:'last-name']
In user model. Both first_name and last_name have validates … presence: true. However, client, is trying to hit the endpoint with just attributes[first-name]. Note, attributes[last-name] is not being passed in the request. Rails thinks that #user.first_name has a value, but #user.last_name is nil. So a validation error is thrown
One way I thought of going about this was something like:
#user.first_name = user_params[:attributes][:'first-name'].present? ? user_params[:attributes][:'first-name'] : #user.first_name
#user.last_name = user_params[:attributes][:'last-name'].present? ? user_params[:attributes][:'last-name'] : #user.last_name
Is this a viable approach? Or is there something better I can consider?
EDIT. A more sophisticated problem is when I need to pre-calculate before actually saving the object. Take for example a product trying to update its price against a discount value, if present
def update
product = Product.find(params[:id])
product.amount_in_cents = product_params[:attributes][:'amount-in-cents']
product.discount_in_percentage = product_params[:attributes][:'discount-in-percentage'].present? ? product_params[:attributes][:'discount-in-percentage'].to_f : nil # Can be 0.10
if product.discount_in_percentage.present?
product.amount_in_cents = product.amount_in_cents + (product.amount_in_cents * product.discount_in_percentage)
else
product.amount_in_cents = product.amount_in_cents
end
if product.save
# ...
end
end
In Rails, we have a convention that the attributes in Model should be fetched to the Rails App like user[first_name] , user[last_name] and in controller we build a private method like users_params which will represent the data to be fed to the User model. like
# in controller
def update
user = User.find(params[:id])
user.update(users_params)
end
private
# This will prepare a whitelisted params data
def users_params
params.require(:user).permit(:first_name, :last_name, ...)
end
No need of this
#user.first_name = user_params[:attributes][:'first-name'].present? ? user_params[:attributes][:'first-name'] : #user.first_name
#user.last_name = user_params[:attributes][:'last-name'].present? ? user_params[:attributes][:'last-name'] : #user.last_name
In your case, you need to reformat the params keys to first_name instead of first-name and so forth. This will help you do your stuff with ease.
Tip: Try to keep it simpler as possible
Im quite a begginer on ruby ; I ve been trying to use roo and still cannot import anything ! after hours of search, I guess problem comes from new rails version which doensn't use accessible_attributes anymore (tutorials are well done, but they all use this command...)
there is no
here is the model code i am trying to fix.
#attr_accessible :name, :price, :released_on
#validates_presence_of :price
def self.open_spreadsheet(file)
case File.extname(file.original_filename)
when '.csv' then Roo::Csv.new(file.path)
when '.xls' then Roo::Excel.new(file.path)
when '.xlsx' then Roo::Excelx.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
def self.import(file)
spreadsheet = open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash.slice #(*accessible_attributes)
product.save!
end
end
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash
product.save!
end
And make sure you've set correct permissions in your controller like that:
def product_params
params.require(:product).permit(:name, :price, :released_on)
end
I m using cancan(1.6.10) with rails 4.0.0. I have a model called 'App'(not scoped) and a controller Admin::AppsController(its scoped. ie app/controllers/admin/apps_controller).
the controller code is as
class Admin::AppsController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource class: App
def index
end
#CRUD methods and some other custom methods
...
private
def app_params
params.require(:app).permit(:name, :description, :author, :url_path, :validated, :active, :version)
end
end
I m getting error when i try to create a 'app'.
ActiveModel::ForbiddenAttributesError - ActiveModel::ForbiddenAttributesError:
activemodel (4.0.0) lib/active_model/forbidden_attributes_protection.rb:21:in `sanitize_for_mass_assignment'
I added
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
as specified in https://github.com/ryanb/cancan/issues/835#issuecomment-18663815 but still getting the above error.
Using name spaces. Please try to change your code to this one below. I had same issue after #JiriKolarik suggested his solution to work with name spaces. I hope it helps.
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
if you use this workflow
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
then your params method should look like this
def admin_app_params
params.require(:admin_app).permit(:name, :description, :author, :url_path, :validated, :active, :version)
end
The reason why, it's because form generators (form_form, simple_form) generate params with namespace_resource
So if you have Blog::Post, form generator will create params like this
{ "blog_post"=>{"title"=>"Post"}, "commit"=>"Create", "action"=>"create", "controller"=>"blog/posts", "locale"=>"en"}
And this is how before filter works:
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym # => 'blog/posts' => 'blog/post' => 'blog_post' => :blog_post
method = "#{resource}_params" # => 'blog_post_params'
params[resource] &&= send(method) if respond_to?(method, true) # => params[:blog_post]
end
If you need read :blog_post from params, solution above will not work. If you need read :post from params, then this solution will not work, if your controller will be blog/post
cancan just does not work with the strong parameter. While there is a new gem cancancan which works well without any code change.
I have a before_validation :do_something, :on => :create in one of my models.
I want to test that this happens, and doesn't happen on :save.
Is there a succinct way to test this (using Rails 3, Mocha and Shoulda), without doing something like:
context 'A new User' do
# Setup, name test etc
#user.expects(:do_something)
#user.valid?
end
context 'An existing User' do
# Setup, name test etc
#user.expects(:do_something).never
#user.valid?
end
Can't find anything in the shoulda API, and this feels rather un-DRY...
Any ideas? Thanks :)
I think you need to change your approach. You are testing that Rails is working, not that your code works with these tests. Think about testing your code instead.
For example, if I had this rather inane class:
class User
beore_validation :do_something, :on => :create
protected
def do_something
self.name = "#{firstname} #{lastname}"
end
end
I would actually test it like this:
describe User do
it 'should update name for a new record' do
#user = User.new(firstname: 'A', lastname: 'B')
#user.valid?
#user.name.should == 'A B' # Name has changed.
end
it 'should not update name for an old record' do
#user = User.create(firstname: 'A', lastname: 'B')
#user.firstname = 'C'
#user.lastname = 'D'
#user.valid?
#user.name.should == 'A B' # Name has not changed.
end
end
You might like the shoulda callback matchers.
I've just implemented OmniAuth (using Ryan Bates' Screencast http://asciicasts.com/episodes/235-omniauth-part-1) and am writing Rspec tests for the functionality and have ran into trouble testing the authentifications#create action. I'm at quite a loss as to how to test this one -- in particular how to stub the local variable omniauth. No matter what I try I keep can't get any tests to work.
Taking a cut down version of the action, how would you test that a new is called on User for example
#cut down version of the authentifications controller code I am attempting to test
def create
omniauth = request.env["omniauth.auth"]
authentification = Authentification.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
....
user = User.new
....
end
#example test
it "should create a new user" do
subject.stub_chain(:request,:env) {{"omniauth.auth" => {'provider' =>1, 'uid' => 2}}}
User.should_receive(:new)
post :create
end
I Did that :
class SessionsController < ApplicationController
def create
#user = User.find_by_auth_hash(auth_hash)
end
def auth_hash
request.env['omniauth.auth']
end
end
describe SessionsController do
it 'should allow login' do
controller.stub!(:auth_hash).and_return({'provider' => 'twitter', 'uid' => '1234'})
get :create, :provider => 'twitter'
assigns(:user).should_not be_nil
end
end
Hope that helps.