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
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
In an edit form, an existing password appears in the form as blank, which seems to be the default Rails behavior. I'm trying, however, to avoid the password (or in my case passwords) from being updated to blank if a new password isn't entered, similar to what is described here:
Rails Activerecord update saving unedited field as blank
The difference for me is that the password field is more deeply nested and there is more than one.
Basically what I have is a small bank transfer app where for every :transfer there are two :transfer_accounts, source and destination (transfer_accounts is a "has_many, through" join table for transfers and accounts) and both transfer accounts have an :account with a :password attribute.
My attempt was something like this at the top of the update action:
params[:transfer][:transfer_accounts_attributes].each do |k, v|
v[:account_attributes][:password].delete if v[:account_attributes][:password].empty?
end
which didn't work. Either password left blank is updated to blank.
How would I iterate through the params and prevent either or both passwords from updating if they are left blank?
Here is my controller:
class TransfersController < ApplicationController
def new
#transfer = Transfer.new
#transfer.transfer_accounts.build(account_transfer_role: 'source').build_account
#transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account
#valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def index
#transfers = Transfer.all
end
def show
#transfer = resource
end
def create
#transfer = Transfer.new(transfer_params)
if #transfer.save
redirect_to transfers_path, notice: "Transfer Created"
else
redirect_to transfers_path, alert: "Transfer Not Created"
end
end
def edit
resource
#valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def update
if resource.update_attributes(transfer_params)
redirect_to transfers_path(resource), notice: "Transfer Updated"
else
redirect_to edit_transfer_path(resource), alert: "Transfer Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
#transfer ||= Transfer.find(params[:id])
end
def transfer_params
params.require(:transfer).
permit(:name, :description,
transfer_accounts_attributes:
[:id, :account_transfer_role,
account_attributes:
[:id, :bank_id, :name, :description, :user_name,
:password, :routing_number, :account_number
]
])
end
end
params[:transfer][:transfer_accounts_attributes].each do |k, v|
v[:account_attributes].delete(:password) if v[:account_attributes][:password].blank?
end
You have to call hash.delete, rather than delete the contents of the already blank value. Also .blank? is your friend, since that will take care of nil and == ''.
I have looked and tried various things in regards to creating a gem with a generator. Maybe my tiredness is causing me to forget something or maybe its just my lack of experience. Either way I am trying to understand how to build a gem that is a simple generator so that I can reuse the code in future projects. Yes I am building something that already exists but as a learner I am more interested in understanding how to build a gem so that I may be able to contribute something more meaningful in the future rather than just using already made gems without knowing what is really going on. So with out further ado my code looks like this:
tree for simpauth
-lib
-generators
-simpauth
-templates
sessions.rb
install_generator.rb
-simpauth
simpauth.rb
here is my code for generators/simpauth/install_generator.rb
require 'rails/generators'
module Simpauth
class InstallGenerator < ::Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
desc "Creating simple and customizable authentication"
def add_session
copy_file "sessions.rb", "app/controllers/sessions_controller.rb"
end
end
end
my generators/simpauth/templates/sessions.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email])
if user && user.authenticate(params[:session][:password])
#login user and redirect to user_path
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_to user
else
flash.now[:danger] = "invalid email and/or password"
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
and lib/simpauth.rb
require "simpauth/version"
require 'rails'
module Simpauth
class Engine < Rails::Engine
end
end
also simpauth.gemspec
# coding: utf-8
$:.push File.expand_path('../lib', __FILE__)
require 'simpauth/version'
Gem::Specification.new do |spec|
spec.name = "simpauth"
spec.version = Simpauth::VERSION
spec.authors = ["My Name"]
spec.email = ["my_email#example.com"]
spec.summary = %q{Simplified authentication}
spec.description = %q{}
spec.homepage = ""
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.7"
spec.add_development_dependency "rake", "~> 10.0"
end
Any help would be greatly appreciated.
Edit - this code works as expected inside a rails app. I just can't get rails to recognize the generator when installed as a gem.
I was able to figure out that my problem was related to my gemspec file. Specifically with the spec.file assignment. I changed:
spec.file = `git ls-files -z`.split("\x0")
to
spec.file = Dir["{lib,vendor}/**/*"]
which resolved my issues.
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
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.