Rails 4: update_attributes with different role - ruby-on-rails

In rails 3 we could use update_attributes as below
#customer.update_attributes(params[:customer], :as => :default)
or
#customer.update_attributes(params[:customer], :as => :admin)
and our attributes accessible would be defined as
attr_accessible :fname, :lname, :as => [:default, :admin]
attr_accessible :status, :as => [:admin]
But in rails 4, update_attributes does not accept second parameter. So how can I convert the above code to work in rails 4?
I tried something like this
#customer.update_attributes(customer_params)
private
def customer_params
params.require(:customer).permit(:fname, :lname, :status )
end
But I don't know how to pass the role in rails 4 while updating attributes. I can not do something like this.
#customer.update_attributes(customer_params, :as => :admin)
This is not allowed in rails 4. So how can I do similar thing in rails 4?

Have a look at strong parameters. The permitted attributes should be determined in the controller not the model
http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters
For example the controller would have
def customer_params
if current_user.admin?
params.require(:customer).permit(:fname, :lname, :status)
else
params.require(:customer).permit(:fname, :lname)
end
end
def update
#customer.update_attributes(customer_params)
...
end

Related

How to merge default values into nested params in Rails 5?

I am desperately trying to merge a set of default values into my nested params. Unfortunately, using deep_merge no longer works in Rails 5 since it no longer inherits from Hash.
So this does not work:
class CompaniesController < ApplicationController
def create
#company = current_account.companies.build(company_params)
if #company.save
flash[:success] = "Company created."
redirect_to companies_path
else
render :new
end
end
private
def company_params
params.require(:company).permit(
:name,
:email,
:people_attributes => [
:first_name,
:last_name
]
).deep_merge(
:creator_id => current_user.id,
:people_attributes => [
:creator_id => current_user.id
]
)
end
end
It gives me this error:
undefined method `deep_merge' for ActionController::Parameters:0x007fa24c39cfb0
So how can it be done?
Since there seems to be no similar implementation of deep_merge in Rails 5 ActionController::Parameters by looking at this docs, then you can just simply do .to_h to convert it first into a ActiveSupport::HashWithIndifferentAccess (which is a subclass of a Hash):
to_h() Returns a safe ActiveSupport::HashWithIndifferentAccess representation of the parameters with all unpermitted keys removed.
def company_params
params.require(:company).permit(
:name,
:email,
:people_attributes => [
:first_name,
:last_name
]
).to_h.deep_merge(
:creator_id => current_user.id,
:people_attributes => [
:creator_id => current_user.id
]
)
end

active admin image field is shown dirty

I am using ActiveAdmin gem for admin console in ROR application. In my ActiveAdmin model-FeaturedEvent i have declared form for creating Featured event. FeaturedEvent model as a image field so i tried writing it. but it says NoMethodError in Admin::FeaturedEvents#new undefined method `new' for nil:NilClass. Following is my ActiveAdmin Model:
ActiveAdmin.register FeaturedEvent do
permit_params do
permitted = [:name, :location, :start_time, :description,:image,:phone, :email, :event_url, :active, :free, :image, :s3_credentials]
permitted << :other if params[:action] == 'create'
permitted
end
controller do
def create
byebug
#featuredevent = Event.new(permitted_params[:featuredevent])
if #featuredevent.save
redirect_to '/admin/featured_events#index'
else
flash[:message] = 'Error in creating image.'
end
end
def event_params
params.require(:event).permit(:name, :location, :start_time, :description,:image,:phone, :email, :event_url, :active, :free, :image, :s3_credentials)
end
end
form do |f|
inputs 'Create Private Events' do
input :image
end
actions do
button_to 'Create', featured_speakers_path(:featuredevent), method: :post
#link_to 'Create', {:controller => 'events', :action => 'create'}, {:method => :post }
end
end
end
Error in browser on navigating to create featured event:
**NoMethodError in Admin::FeaturedEvents#new**
undefined method `dirty?' for nil:NilClass
Ok i got it. Paperclip needs a different name for attachment declaration in FeaturedEvent model [I have other model in which image was used ]. Thank you Vishal

Rails 4 strong params get permit from nested model

There are several questions for strong params, but I couldn't find any answer for achieving my goal. Please excuse any duplicates (and maybe point me in the right direction).
I'm using strong params in a model that has several 'has_one' associations and nested attributes with 'accepts_attributes_for'.
In my routes I have: (updated for better understanding)
resources :organisations do
resources :contact_details
end
So, i.e. for one associated model I have to use
def organisation_params
params.require(:organisation).permit(:org_reference, :supplier_reference, :org_type, :name, :org_members, :business, :contact_person, contact_detail_attributes: [:id, :contactable_id, :contactable_type, :phone, :fax, :mail, :state, :province, :zip_code, :street, :po_box, :salutation, :title, :last_name, :first_name, :description])
end
This works, but I have to retype all my permitted params for each associated model. When I modify my permitted attributes for contact_details , I have to change it in several locations (every model that has the polymorphic association).
Is there a way to get the parameter whitelist of contact_details and include it into the parent whitelist?
Something like:
def organisation_params
my_params = [:org_reference, :supplier_reference, :org_type, :name, :org_members, :business, :contact_person]
contact_params = #get permitted params, that are defined in contact_details_controller
params.require(:organisation).permit(my_params, contact_params)
end
I don't want to workaround security, but I had already defined the permitted attributes for the contact_details and don't want to repeat it in every associated "parent" model (because it's exhausting and very prone to stupid mistakes like omitting one attribute in one of several parent models).
Use a method defined inside ApplicationController, or a shared module:
ApplicationController:
class ApplicationController
def contact_details_permitted_attributes
[:id, :contactable_id, :contactable_type, ...]
end
end
class ContactDetailsController < ApplicationController
def contact_details_params
params
.require(contact_details)
.permit(*contact_details_permitted_attributes)
end
end
class OrganisationsController < ApplicationController
def organisation_params
params
.require(:organisation)
.permit(:org_reference, ...,
contact_detail_attributes: contact_details_permitted_attributes)
end
end
Shared module:
module ContactDetailsPermittedAttributes
def contact_details_permitted_attributes
[:id, :contactable_id, :contactable_type, ...]
end
end
class ContactDetailsController < ApplicationController
include ContactDetailsPermittedAttributes
def contact_details_params
params
.require(contact_details)
.permit(*contact_details_permitted_attributes)
end
end
class OrganisationsController < ApplicationController
include ContactDetailsPermittedAttributes
def organisation_params
params
.require(:organisation)
.permit(:org_reference, ...,
contact_detail_attributes: contact_details_permitted_attributes)
end
end
Rails has even dedicated directories for shared modules, concerns inside app/controllers and app/models; indeed, in your case you should use app/controllers/concerns
I don't see why not. In your ApplicationController you could have
def contact_attributes
[:id, :contactable_id, :contactable_type, :phone, :fax,
:mail, :state, :province, :zip_code, :street, :po_box,
:salutation, :title, :last_name, :first_name, :description]
end
Then in your organisation_params
def organisation_params
my_params = [:org_reference, :supplier_reference, :org_type, :name, :org_members, :business, :contact_person]
params.require(:organisation).permit(*my_params, contact_detail_attributes: contact_attributes)
end
In some other location you might do...
def contact_params
params.require(:contact).permit(*contact_attributes)
end

Rails - How to declare attr_accessible for multiple roles without duplication

Is there a way to declare attr_accessible for multiple roles without a ton of duplication?
If I have several user roles, and each role is allowed to edit a different subset of attributes, here's what my attr_accessible declaration looks like:
attr_accessible :first_name, :last_name, :active, :as => :admin
attr_accessible :first_name, :last_name, :as => :manager
attr_accessible :first_name, :last_name, :as => :guest
I'd like to either
A) define an array of accessible attributes that can be shared among
different roles or
B) define an array of roles than can access the same
attributes
Is this possible?
I just spent a long time trying to figure out the best way to do this. It seemed strange that the rails folk would expect you to duplicate a whole bunch of code!
After some digging around in the rails source, it turns out you can simply pass an array to assign attributes to multiple roles at once (:default being the default Active Record role)
attr_accessible :name, :email, :as => [ :default, :admin ]
attr_accessible :featured, :as => :admin
No messy ruby arrays in your model!
All ruby code is still just ruby code... and is thus infinitely hackable. eg
ROLES = [:admin, :manager, :support, :user, :guest]
ACTIVE_ROLES = [:admin, :support]
ROLES.each do |role|
fields = [:first_name, :last_name]
fields += [:active] if ACTIVE_ROLES.include?(role)
attr_accessible *fields, :as => role
end
Did you try something like:
COMMON_FIELDS = [:first_name, :last_name]
attr_accessible COMMON_FIELDS | [:active, :as => :admin]
attr_accessible COMMON_FIELDS | [:as => :manager]
attr_accessible COMMON_FIELDS | [:as => :guest]
Another possible way (untested):
attr_accessible :first_name, :last_name
ADMIN_ACCESSIBLE = [:active]
MANAGER_ACCESSIBLE = []
GUEST_ACCESSIBLE = []
protected
def mass_assignment_authorizer
if role == :all
self.class.protected_attributes
else
super + (eval("#{role}_accessible".upcase) || [])
end
end

virtual attribute problems with undefined methods

I've used Virtual attributes in the past but I can't seem to get past this, and I know the answer is probably staring me in the face.
I have a model like so:
model Confirmation.rb
class Confirmation < ActiveRecord::Base
#attr_accessible :confirmation, :confirmation_token
#attr_accessible :confirmation_token
def confirmation_token
confirmation.confirmation_token if confirmation
end
def confirmation_token=(token)
self.confirmation = Booking.find_by_confirmation_token(token)
end
end
Your average scaffold controller for
confirmations_controller.rb
def new
#confirmation = Confirmation.new(:confirmation_token => params[:confirmation_token])
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #confirmation }
end
end
new.html.erb
<h1>New confirmation</h1>
<% form_for(#confirmation) do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :confirmation_token %>
...
routes.rb
map.confirmation "confirmation/:confirmation_token", :controller => "confirmations", :action => "new"
map.resources :confirmations
error
undefined method `confirmation=' for #
In the console Booking.find_by_confirmation_token(token) with a given token works perfectly fine.
Any ideas? suggestions?
What you really need is attr_accessor :confirmation. There's a difference between attr_accessible and attr_accessor.
attr_accessor :confirmation
is same as
def confirmation
#confirmation
end
def confirmation=(value)
#confirmation = value
end
Now since it's such a common pattern ruby introduced helper methods for that.
Attr_accesible on the other hand is rails method, which marks that certain fields can be mass updated.
I think it should be either:
def confirmation_token=(token)
#confirmation = Booking.find_by_confirmation_token(token)
end
Or you should uncomment attr_accessible :confirmation or define #confirmation and #confirmation=.
class Confirmation < ActiveRecord::Base
belongs_to :bookings
#attr_accessible :confirmation, :confirmation_token
#attr_accessible :confirmation
def confirmation_token
#confirmation.confirmation_token if #confirmation
end
def confirmation_token=(token)
#confirmation = Booking.find_by_confirmation_token(token)
end
end
this worked... however just uncovering the attr_accessible :confirmation, did not. self.confirmation still returned undefined method...

Resources