How to add virtual attributes on the fly with strong parameters - ruby-on-rails

I have this controller:
class AccountsController < ApplicationController
def create
if #current_account.update_attributes(account_params)
redirect_to :dashboard
end
end
def account_params
params.require(:account).permit(:company, users_attributes: [:name, phone_attributes: [:number]])
end
end
as you can see, i have 2 levels of nested attributes, the issue is I used to add a virtual attribute before using strong parameters like this:
def create
#phone = #current_account.users.phone.requires_us_format = true
...
end
How to achieve this using both nested attributes and strong parameters?

Related

Using draper gem with devise registration controller

I have declared user_decorator.rb instead of user_helper.rb in the following way
class UserDecorator < Draper::Decorator
delegate_all
def contract_type
contract_types.keys.collect {|k| [k.humanize, k]}
end
def employee_type
employee_types.keys.collect {|k| [k.humanize, k]}
end
def department_role
department_roles.keys.collect {|k| [k.humanize, k]}
end
end
and here are my enums that are declared on the user.rb
enum contract_type: [:probationary, :apprenticeship, :six_months_to_one_year,
:three_years]
enum department_role: [:ceo, :cto, :coo, :manager, :tl, :gl, :developer]
enum employee_type: [:full_time, :part_time, :internship]
I want to call the helper method from the view that is related to registrations controller. It is like as
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
super
end
end
But if I call the helper method, like as following from the views/devise/registrations/new.html.erb
<%= f.select :contract_type, contract_type, {prompt: t(".contract_type",
default: "Select contract type")} %>
It don't find the contract_type. Need help about how I can access the helper methods from the view that is declared on the user_decorator.rb
In order to get a decorated user object from Devise's current_user method, you can override it:
class ApplicationController < ActionController::Base
def current_user
UserDecorator.decorate(super) unless super.nil?
end
end
However, since your issue is on the devise/registrations/new.html.erb page, at this point a user will not have logged in and hence won't have a any kind of decorated current_user object.
So, what it would seem you want is to get the set of decorated contract types from your User model into that view, which you could do by creating an instance variable on the controller:
class RegistrationsController < Devise::RegistrationsController
def new
#contract_types = User.contract_types.keys.collect {|k| [k.humanize, k]}
super
end
end
and then use it in the view:
<%= f.select :contract_type,
#contract_types,
{ prompt: t(".contract_type", default: "Select contract type") } %>
If you wanted to do further refactorings, you could perhaps create a scope-like class method on your User model that does the decoration of the contract types:
class User < ActiveRecord::Base
def self.humanized_contract_types
contract_types.keys.collect {|k| [k.humanize, k]}
end
end
So, then your controller code could be shortened to:
class RegistrationsController < Devise::RegistrationsController
def new
#contract_types = User.humanized_contract_types
super
end
end
Or, if you're intent on using the UserDecorator class, you could look into proxying class method calls to it.
You can override your new method to decorate your resource like so :
# registrations_controller.rb
def new
build_resource({})
yield resource if block_given?
respond_with resource
end
protected
def build_resource(hash = nil)
self.resource = resource_class.new_with_session(hash || {}, session).decorate
end

Rails 4 - Strong Params - white labelling foreign key

I have a rails 4 app.
When creating permitted params in a controller where there is a belongs_to association with another model, do you need to include the foreign key in the permitted params, so that it can be updated when the record is saved, or is that automatic?
It's NOT automatic.
In Rails 4, you have to permit the attribute in order to be able to mass assign its value. The foreign key of the other model is an attribute of your current model that you're trying to update. Without permitting that, you can't update it's value.
The foreign key is not automatic, the associated object is:
This means the following is true:
#app/controllers/your_controller.rb
class YourController < ApplicationController
def create
#item = Item.new new_params
#associated = Associated.find x
#item.associated = #associated #-> this always works & will save
#item.save
end
private
def new_params
params.require(:item).permit(:name, :etc) #-> foreign_key would have to be explicitly defined here if associated_id was passed from a form
end
end
This should give you some perspective on what you can do with your objects.
Update
If you want to assign a post to the current user each time, you'd be able to use the following:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
#post = Post.new post_params
#post.user = current_user # -> however you identify the user
#post.save
end
end

Rails 3.2 model constructor with Rails 4

My model constructor:
3.2:
def initialize(attributes = {})
super # must allow the active record to initialize!
attributes.each do |name, value|
send("#{name}=", value)
end
end
4.0.4:
ActiveModel::ForbiddenAttributesError
How do I change the 3.2 constructor for 4.0.4 compatibility?
I guess you are getting this error when you try to create a new user from your User controller?
If you have code like:
class UsersController < ApplicationController
...
def create
#user = User.new(params[:user])
...
end
...
end
Then this won't work because you can no longer do mass assignments in Rails 4.
Instead you need to whitelist the parameters in the controller something like this:
class UsersController < ApplicationController
...
def create
#user = User.new(user_params)
...
end
...
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :email) # etc. according to your user model
end
end
Having said this, I'm not sure why you need the constructor in your model at all? The initialize method you get from inheriting from ActiveRecord::Base should be enough. But in any event you will need to whitelist the parameters in your controller to avoid the ForbiddenAttributesError error.

Setting nested attributes in the model rails

I have a Log model that belongs to User and Firm. For setting this I have this code in the logs_controller's create action.
def create
#log = Log.new(params[:log])
#log.user = current_user
#log.firm = current_firm
#log.save
end
current_user and current_firm are helper methods from the application_helper.rb
While this works it makes the controller fat. How can I move this to the model?
I believe this sort of functionality belongs in a 'worker' class in lib/. My action method might look like
def create
#log = LogWorker.create(params[:log], current_user, current_firm)
end
And then I'd have a module in lib/log_worker.rb like
module LogWorker
extend self
def create(params, user, firm)
log = Log.new(params)
log.user = user
log.firm = firm
log.save
end
end
This is a simplified example; I typically namespace everything, so my method might actually be in MyApp::Log::Manager.create(...)
No difference: You can refactor the code:
def create
#log = Log.new(params[:log].merge(:user => current_user, :firm => current_firm)
#log.save
end
And your Log have to:
attr_accessible :user, :firm
Not much shorter, but the responsibility for the handling of current_user falls to the controller in MVC
def create
#log = Log.create(params[:log].merge(
:user => current_user,
:firm => current_firm))
end
EDIT
If you don't mind violating MVC a bit, here's a way to do it:
# application_controller.rb
before_filter :set_current
def set_current
User.current = current_user
Firm.current = current_firm
end
# app/models/user.rb
cattr_accessor :current
# app/models/firm.rb
cattr_accessor :current
# app/models/log.rb
before_save :set_current
def set_current
self.firm = Firm.current
self.user = User.current
end
# app/controllers/log_controller.rb
def create
#log = Log.create(params[:log])
end

Using the save method together with update_attributes. Is this common?

A user can sign up as an artist. All the user needs to do now, is provide his email.
In Artist controller, def create. Is it normal to have something like:
def create
#artist = current_user
respond_to do |format|
if #artist.update_attributes(params[:user]) # params[:user] contains email
#artist.is_artist = true
#artist.save
....
In my User model, I have:
attr_accessible :email
Which means, I can't simply do #artist.update_attributes(:is_artist => true). I would have to use the save method instead. Is this type of approach common? Or is there a better way?
You can define before_create method in your model:
class User < ActiveRecord::Base
...
before_create :fill_fields
def fill_fields
is_artist = true
end
end
I would do the following:
1st: I wound not set up an ArtistController if you do not have an Artist Model. rather I would add a non-restful method in your UserController, and push the implemention logic into the model ...
# config/routes.rb
resources :users do
member {post 'signup_as_artist'}
end
# UserController
def signup_as_artist
#user = User.find(params[:id])
#user.signup_as_artist
end
# User
def signup_as_artist
self.update_attribute :is_artist, true
end
Good luck

Resources