I have a simple nested form and I am setting a nested attribute with a hidden field:
<%= role_form.hidden_field :company_id, :value => session[:company_id] %>
The idea here is I am associating this nested model (a role based permissions system) to another model Company via the company_id set by the current session variable. My issue is that the user could send a request and create / update the role with an arbitrary company_id and gain access to another company's account.
Can I force the nested model attributes to be this session value or perhaps a validation?
I was thinking for create:
#user = User.new(params[:user])
#user.roles.first.company_id = session[:company_id]
and for update I could do sort of the same thing.
As for the validation I tried:
accepts_nested_attributes_for :roles, :limit => 1, :allow_destroy => true , :reject_if => proc { |attributes| attributes['company_id'] != session[:company_id] }
but it looks like you can't access the session info in the model.
Any one have an idea if I can do this either of these ways?
Rather than storing the company_id in the session, you should instead add a randomly generated token column to the company, and get the id by doing Company.find_by_token(session[:token]). If you look at how the current_user method in this Railscast on authentication, it's the same idea.
Edit:
Sorry, I misunderstood your question. You should not have a hidden company_id field at all in your view. You should be setting it manually in your create method:
#user = User.new(params[:user])
#user.company_id = session[:company_id]
And you can protect the company_id from ever being set from the user changing an input name by having company_id protected against mass assignment in the model:
attr_protected :company_id
See the rails guide on mass assignment protection for more information. Note: a more common solution is something along these lines:
class ApplicationController < ActionController::Base
protect_from_forgery
def current_company
#current_company ||= Company.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
end
class User < ApplicationController
def create
#user = current_company.users.build(params[:user])
end
end
UPDATE 2:
So you're creating a user and a role, and want to do separate validation on them, this should do what you want.
role_params = params[:user].delete :role # Change to the appropriate symbol for your form
#user = User.new(params[:user])
role = #user.roles.build(role_params)
role.company_id = session[:company_id]
if(#user.save and role.user_id = #user.id and role.save) # Might want to just check for valid instead of trying to save
...
Related
Basically i have user registering himself to the app , by using devise gem.
Instead of having standard sign up form like (email, password) i have an extra 2 fields (name, contact_nr) in total used (name, contact_nr, email, password, password_confirm) fields, :name and :contact_nr attributes exists in 'clients' table only.
Table name: clients
id :integer not null, primary key,
name :string(255)
surname :string(255)
contact_nr :string(255)
user_id :integer
class Client < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :client, dependent: :destroy
after_create :update_user_client
def name
return unless client
client.name
end
def contact_nr
return unless client
client.contact_nr
end
def update_user_client
Client.last.update_attributes(user: self)
end
end
In my RegistrationsController I have only one method
class RegistrationsController < Devise::RegistrationsController
before_action :create_client
private
def create_client
return if params[:user].blank?
Client
.new(name: params[:user][:name],
contact_nr: params[:user][:contact_nr])
.save(validate: false)
end
end
What bothers me is that kind of writing code, it feels like code smell.
How would you implement it?
Thanks guys looking forward to your answers..
First advice I can give is do not separate client and user into two tables if you don't have valid reasons and/or requirements for now. That would make things much easier.
If you have valid reasons, here are my advices on how to improve your existing state of this code piece:
Rails and all mature gems around it rely on 'convention over configuration', so you should check if there are conventional ways to achieve same results.
In your RegistrationsController instead of doing params[:user].blank? check, you should use Devise's way of doing this, provided with inherited methods as devise_parameter_sanitizer.permit within a before_action callback.
Instead of creating client in your controller, move that to model logic, and in your user model put accepts_nested_attributes_for :client.
Since both of your models(client and user) share same name, put a before_save callback, so that you can pass user's name attribute to client itself.
after_create callback is very risky, since it is not an atomic save (no guarantee that client will be updated after user record is updated.). So don't use it. accepts_nested_attributes_for will handle both create and update calls.
If name attribute for user would be fetched through client only, there is no need to keep name within user.
If you want to access client's contact_nr and name attributes directly from user model, then use delegate method inside it.
Putting all together, I would refactor that code piece as this:
class User < ActiveRecord::Base
has_one :client, dependent: :destroy
accept_nested_attributes_for :client
delegate :name, to: :client
delegate :contact_nr, to: :client
# optional. if you want to keep name attr in both models.
before_save :sync_names
private
def sync_names
self.client.name = name if client.present?
end
end
class RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters
protected
def configure_permitted_parameters
added_attrs = [:name, :email, :password, :password_confirmation, client_attributes: [:contact_nr]]
devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
devise_parameter_sanitizer.permit :account_update, keys: added_attrs
end
end
Don't forget to update your signup and account update forms to accept nested attributes for client resource.
As far as you are validating the data with JS and filtering with params.require(:client).permit, the code looks fine. Try to create many differente scenarios in your Rspec. The test usually reveals unexpected flaws.
Okey i just dont understand what can be wrong here
i have this app where users are approved by and admin and this was working fine until a few days ago
in my view i have a link that calls my user controller
<%= link_to 'Approve', active_user_path(user), :method => :put %>
here is my custum route for that link
match "users/:id/activate" => "users#activate", :as => "active_user"
now in my user controller i have this activate method
def activate
#user = User.find(params[:id])
puts #user.name #the correct name is displayed
puts #user.is_approved.inspect.to_i #:is_approved is 0
if #user.update_attribute(:is_approved, 1)
puts #user.is_approved.inspect # :is_approved is 1
#user.activate_user
puts #user.is_approved.inspect # :is_approved is 1
#user.save!
redirect_to "/users?is_approved=0"
else
render "/" # dosn't matter
end
end
I try to save 3 times here (update, activate_user, save!) but still the value will not be saved, the users is_approved field is still 0, how is that possible ?
here is my model method
def activate_user
self.is_approved = 1
self.save
end
btw i can update strings with this method but not integers (true and false dosnt work either)
in my model i have is_approved as both attr_accessible and attr_accessor
The solution
Well this is awkward but so it happens that in my user model i had attr_accessor :approved this resulted in that the model never went to the database to update the :approved column BUT instead it updated the local variable :approved so next time when i looked at the column then of course the :approved value had not changed
tldr?
if you have attr_accessor in your model with the same name as the column your trying to update => remove it
Never use attr_accessor on an attribute which is backed by a database column - the accessor generated by attr_accessor will mask the value stored in the database
update_attribute actually does more than just updating a single column:
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
Are there any callbacks in your User model?
Make sure the column is not being updated somewhere in a callback.
I have a User class defined like this
class User
end
And I have sublassed this to create an Owner class and created a has_one relationship with another Company class
class Owner < User
has_one :company
end
class Company
belongs_to :owner
end
In my Users controller when creating a new User I want to accomplish the following:
Create a new User
Create a new Company
Associate the User with the Company (as the Owner i.e. company.owner_id)
I can accomplish this with the following code (simplified for brevity)
def create
#user = User.new(params[:user])
#company = Company.new(params[:company])
if #user.save
#company.owner_id = #user.id
#company.save
...
Now, this just feels ugly to me, but I can't seem to get the whole build_asociation process to work as expected (yes, fields are there in both dev and test).
What should I be doing here?
If you need to create Owner and Company at the same time, I suggest you use accepts_nested_attributes_for in Owner. Here's the code:
class Owner < User
has_one :company
accepts_nested_attributes_for :company
end
Then in your controller you can do something like this:
def create
#user = User.new(params[:user]) # should it be User or Owner?
#user.company_attributes = params[:company] # assume two separate forms for User and Company
# if you use fields_for, however, company attributes are nested under params[:user] automatically.
if #user.save
# do your job here
end
end
For complete reference, please take a look at Active Record Nested Attributes and the view helper fields_for
Pretty simple setup. I want to make sure my understanding of the ORM is correct.
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through => memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
Now when a user creates a group I want the membership record in the link table to get populated. It should be an atomic(transaction).
class GroupsController < ApplicationController
def create
#group = current_user.groups.build(params[:group])
if #group.save
flash[:notice] = "Group has been created."
redirect_to #group
else
flash[:alert] = "Group has not been created."
render :action => "new"
end
end
end
This doesn't work. The group gets saved but no membership record created in the link table. However using a create vs build works. Is that how it's supposed to work?
What's the best approach here?
This behaviour is by design. As you mentioned, you can either do #group = current_user.groups.create(params[:group]).
Or you can add an additional statement to create a record in the join model's table as :
#group = current_user.groups.build(params[:group])
if #group.save
#group.memberships.create(:user_id => current_user)
# redirect and notify
Well, the reason being simply building #group and saving it does not add an additional record in the join table.
Infact, in this case, #group = current_user.groups.build(params[:group]) is somewhat similar to #group = Group.new(params[:group]). The difference being, in the former case, current_user.groups will contain #group (you can try that in Groups#create before redirect) but doing current_user.reload followed by current_user.groups will yield [].
The best way to do this is somewhat similar to your approach. Have a simple create action as :
def create
#group = Group.new(params[:group])
# if else for save and redirect
However, for this to work the params hash submitted to Groups#create should include user_ids as :
"group"=>{"name"=>"new group", "user_ids"=>["1", "2", "3"]}, "commit"=>"Create Group"
May be that was the reason why #bruno077 was asking you to paste your view's code, so as to get an idea on user_ids params being passed.
So, if the new group form contains fields to select multiple users, then its simple create action as shown right above (because of the user_ids params). But if have a new group form with no options to select users, then you are better off using the first option (one using create).
I am using Devise for authentication, and when someone visits a specific URL (with specific params in the URL), I would like to set the 'current_user' to a specific value from the db.
How do I do that ?
Also, will I have to make sure all the validation requirements are satisfied ? E.g., my user model has:
validates_presence_of :username, :email
If I set the current_user, would I have to set both the :username & :email, or can I just do one or the other ?
You could try
#user = User.where(:x => "y").first # Find the user depending on the params
sign_in(#user)
# do work
sign_out(#user)
I don't know if that's the best way to do it, but it will set current_user.
devise's current_user helper sets the instance variable #current_user, so doing this:
#current_user = User.first
will also set current_user to the selected user.