In my Rails app I have Users, Roles, and Permissions.
When creating/editing a Role, you can choose which permissions are enabled by checking their checkbox which will save the Permission in a table called 'roles_permissions' (basically on permissions that are allowed are stored in the join table).
So my edit role method is as follows:
def edit
#role = Role.find(params[:id])
#permissions_by_controller = Permission.order('controller asc').group_by(&:controller)
end
and the update method (the patch):
def update
#role = Role.find(params[:id])
if #role.update_attributes(role_params)
redirect_to roles_path, :notice => 'Article updated!'
else
render 'edit'
end
end
and the params:
def role_params
params.require(:role).permit(:name, permission_ids: [])
end
In the edit view I have a checkbox like so:
<%= check_box_tag "role[permission_ids][]", permission.id, #role.permissions.include?(permission), :id => permission.id, :class => 'switch__checkbox' %>
This works fine for when I check the checkbox and it saves.
However when I uncheck the checkbox and save, it doesn't remove the permission... presumably becuase no param is passed back. How do I solve this?
The associations are set as:
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => 'users_roles'
has_and_belongs_to_many :permissions, :join_table => 'roles_permissions'
end
class Permission < ActiveRecord::Base
has_and_belongs_to_many :roles, :join_table => 'roles_permissions'
end
Note: I've also noticed that Roles cannot be removed from a User in the same way as it uses checkboxes.
A classic problem. f.check_box solves this for you, by returning false when unchecked, but check_box_tag does not.
You can solve it with a hidden field with an empty value just before the tag.
<%= hidden_field_tag "role[permission_ids][]", '' %>
<%= check_box_tag "role[permission_ids][]", permission.id...
The form knows to populate the empty value field when nothing is selected. (If it's clearer for you, you can also put 'false' instead of an empty string.)
Complementing steel's answer (and since I can't comment on his answer bc of my rep)
I'll say to anyone looking for a different way to solve this.
I prefer attaching the hidden_field to the form .
<%= form.hidden_field :permission_ids, { multiple: true, value: '' } %>
Since we are calling form.hidden_field, the name and id will already contain role and it will add permissions_ids as a key inside role.
Multiple will add the needed square brackets.
The resulting html tag will be:
<input multiple="multiple" value="" type="hidden" name="role[permission_ids][]" id="role_permission_ids">
Related
I have two models, Chasing and User, a chasing belongs_to :user and a user has_many :chasings.
I created a migration for linking the two models together:
class AddUsersToChasings < ActiveRecord::Migration
def change
add_reference :chasings, :user, index: true, foreign_key: true
end
end
I have a controller for creating new users which I then want to be able to assign to chasings. I currently have this code in my chasings form for selecting the user:
<%= f.select :user_id, options_for_select(User.all.map {|c| [c.name, c.id]}), { :include_blank => "Please select user"}, {:class => "form-control"} %>
This seems to do the trick, after calling Chasing.first in rails console I can see the chasing now has user_id relevant to the user I picked. I can also run Chasing.first.user.name to give me the name of the user who is associated with the chasing. I'm wanting to show this name in my index view, the code I currently have for this is:
ChasingsController:
def index
#chasing = Chasing.all
end
Index view:
<% #chasing.each do |chasing| %>
<%= chasing.user %>
<% end %>
This shows a random string (seems to change every time I update a chasing - #<User:0xf5b0ba8> for example). when I change this to chasing.user.name I get 'undefined method `name' for nil:NilClass'.
Is there a way I can call the name for my view?
EDIT:
As per NickM's comment below I had chasings without users assigned to them causing active record to throw the error.
Looks like you have some Chasing objects in your database without user_ids. You can test by doing <%= chasing.user.name if chasing.user %>
I am using Cocoon gem to do nested forms.
I have models like that:
# request.rb
has_many :filled_cartridges, inverse_of: :request, dependent: :destroy
accepts_nested_attributes_for :filled_cartridges, :reject_if => :all_blank, allow_destroy: true
#filled_cartridge.rb
belongs_to :request
Inside of my form_for #request i have nested form like that:
<div id="filled_cartridges">
<%= f.fields_for :filled_cartridges do |filled_cartridge| %>
<%= render 'filled_cartridge_fields', f: filled_cartridge %>
<% end %>
<div class="links">
<%= link_to_add_association 'add', f, :filled_cartridges %>
</div>
Where filled_cartridge_fields partial is like that:
<fieldset>
<%= f.text_field :cartridge_id %>
<%= f.hidden_field :_destroy %>
<%= link_to_remove_association "remove", f %>
</fieldset>
When i click on "add" it adds one more . When clicking on "remove" it removes that .
When i submit form the params for nested form look like that:
filled_cartridges_attributes: !ruby/hash:ActionController::Parameters
'0': !ruby/hash:ActionController::Parameters
cartridge_id: '12'
_destroy: 'false'
'1429260587813': !ruby/hash:ActionController::Parameters
cartridge_id: '2'
_destroy: 'false'
How do i access these params, and how to save them. How to traverse over these params and save them, or do Cocoon gem has some built in functionality? And finally how to check if these params are set? Since it is nested, it tricks me.
EDIT: My request_controllers#create:
def create
#request = Request.new( request_params )
# code for handling Request model
# here i want to handle nested model too (filled_cartridge)
#request.save
if #request.save
flash[:success] = "Заявка была добавлена"
redirect_to #request
else
render 'new'
end
end
EDIT2: my strong params:
def request_params
params.require(:request).permit(:name, :address, :phone, :mobile, :type, :description, :priority, :responsible, :price, :payed, :date, filled_cartridges_attributes: [:cartridge_id, :_destroy], :stype_ids => [], :social_media =>[])
end
In a recent project using cocoon I had to access the params of the attributes about to be saved. I figured a code in my create action in my controller. The trick is to understand how to retrieve the key of the hash of the attribute that is about to be saved. The key of the hash is that number '1429260587813' that is in your params
...
'1429260587813': !ruby/hash:ActionController::Parameters
cartridge_id: '2'
_destroy: 'false'
So you need to create a loop in your create action to retrieve this key using ruby hash method "keys". I do a loop because when using cocoon dynamic nested field I might create more than one nested attributes at once so it means more than one key to retrieve.
Here is a the code that worked for me, read my comments which explains the different steps of this code. I hope it will help you to adapt it to your needs.
#Here I just initialize an empty array for later use
info_arr = []
#First I check that the targeted params exist (I had this need for my app)
if not params[:recipe]["informations_attributes"].nil?
#z variable will tell me how many attributes are to be saved
z = params[:recipe]["informations_attributes"].keys.count
x = 0
#Initiate loop to go through each of the attribute to be saved
while x < z
#Get the key (remember the number from above) of the first hash (params) attribute
key = params[:recipe]["informations_attributes"].keys[x]
#use that key to get the content of the attribtue
value = params[:recipe]["informations_attributes"][key]
#push the content to an array (I had to do this for my project)
info_arr.push(value)
#Through the loop you can perform actions to each single attribute
#In my case, for each attributes I creates a new information association with recipe
#recipe.informations.new(title: info_arr[x]["title"]).save
x = x +1
end
end
This work to access cocoon nested attribute content and apply actions based on your need. This worked for me so you should be able to use this sample code and adapt it to your need.
I get
ActiveRecord::RecordNotFound: Couldn't find Client with ID=3 for Order with ID=
when trying to submit an Order form for an existing client. This happens through the form or the console by typing:
Order.new(:client_attributes => { :id => 3 })
payment_form.html.erb:
<%= semantic_form_for #order, :url => checkout_purchase_url(:secure => true) do |f| %>
<%= f.inputs "Personal Information" do %>
<%= f.semantic_fields_for :client do |ff| %>
<%= ff.input :first_name %>
<%= ff.input :last_name %>
<!-- looks like semantic_fields_for auto-inserts a hidden field for client ID -->
<% end %>
<% end %>
<% end %>
Order.rb:
class Order < ActiveRecord::Base
belongs_to :client
accepts_nested_attributes_for :client, :reject_if => :check_client
def check_client(client_attr)
if _client = Client.find(client_attr['id'])
self.client = _client
return true
else
return false
end
end
end
The reject_if idea came from here but I logged the method and it's not even being called! It doesn't matter what its name is!
Note: Feb 2020
Since I'm starting to get downvotes on this 8 years later, adding this note. While this was the original solution I went with 8 years ago, a better one has been proposed by MatayoshiMariano (5 years after my OP).
My Original Fix
Fixed the issue by overloading the client_attributes= method, as described here:
def client_attributes=(client_attrs)
self.client = Client.find_or_initialize_by_id(client_attrs.delete(:id))
self.client.attributes = client_attrs
end
If you only want a new Order with an existing client, without modifying the client, you need to assign the id.
Order.new(client_id: 3)
This is another way to do this without overloading the client_attributes= method and cleanest
The new Order now has the client with ID 3
If you also want to update ant client's attributes you must add the client_attributes, for example:
Order.new(client_id: 3, client_attributes: { id: 3, last_order_at: Time.current })
See https://github.com/rails/rails/issues/7256 from 2012.
If you have has_many relationship, this will work. Tested on Rails 6.0.2
def clients_attributes =(attributes)
# Get IDs for any clients that already exist.
client_ids = attributes.values.map { |a| a[:id] }.compact
# Now find them all and move them to this section.
clients << Client.find(client_ids)
# Update them with standard `accepts_nested_attributes_for` behaviour.
super attributes
end
Had the same error creating a new Thing for existing model with has_many and belongs_to relations.
Fixed it by adding a hidden field for the id of the existing model, for instance User, to the form.
= form.input :user_id, as: :hidden
Then new Thing was created without the error.
(rails 2.2.2)
I have 2 models, user and subscription. Each user can have one ore more subscriptions (= premium services). Below the attributes:
user: id, username, ...
subscription: id, user_id (FK), type, started_at, ended_at
The classes:
class User < ActiveRecord::Base
..
has_many :subscriptions, :dependent => :destroy
..
end
class Subscription < ActiveRecord::Base
belongs_to :user, :foreign_key => :user_id
end
Now I want to make the UI part where existing users can subscribe in their account for the premium services. Therefore I wanted to make a first simple version where the user can subscribe by clicking on a checkbox. This is what I get so far
<div class = 'wrapper'>
<%= render :partial => "my_account_leftbar" %>
<% form_for #subscription, :url => subscribe_user_path(current_user) do |f| %>
<div class="field">
<%= (f.check_box :type?) %> <!-- add '?'after the symbol, source: https://github.com/justinfrench/formtastic/issues/269 -->
</div>
<div class="actions">
<%= f.submit "Subscribe", :class => "button mr8" %>
</div>
<% end %>
</div>
Problems:
the app inserts a record into the db, but the attribute I defined in the form (type) has not been set (it should set '1' which stands for 'standard subscription') . How to get the app set this attribute?
how to set the FK? I assume that rails should set the FK automatically, is that assumption correct?
how to set the other values 'started_at' and 'ended_at? Those are datetime(timestamp) values...
Just run out of my beginner rails knowledge, any help really appreciated...
'Type' is a ruby on rails reserved word which should only be used when you are using Single Table Inheritance. You should rename your column name to something else.
I could solve the other questions 2 and 3 as well, wrapping it up:
insert the record: as stated in the answer from Wahaj, renaming the column 'type' into e.g. 'subscription_type' helped. I created a seperate migration as described here: How can I rename a database column in a Ruby on Rails migration?
storing the FK: updated the action in the controller. Instead of just writing
#subscription = Subscription.new(params[:subscription])
I wrote the following method to create a 'user's subscription'
#subscription = current_user.subscriptions.build(params[:subscription])
storing the 'started_at': added a method to the controller:
#subscription.update_attributes(:started_at => Time.zone.now)
I used declarative_authorization for my app and had problem with creating new user.
my User model code:
class User < ActiveRecord::Base
ROLE_TYPES = ["admin", "user", "guest"]
validates_inclusion_of :roles, :in => ROLE_TYPES
def role_symbols
#role_symbols ||= (roles || []).map{|r| r.to_sym}
end
my view code:
<% form_for(#user) do |f| %>
...
<p>
<%= f.label :roles %><br />
<%= f.select :roles, User::ROLE_TYPES, :prompt => "Select a role" %>
</p>
<%= f.submit 'Add User' %>
<% end %>
every time i tried to create a new user and select the role from the drop-down list, the view complaint:
Roles is not included in the list
from the output of the script/server, i can see the roles was actually set:
"user"=>{"name"=>"kc", "password_confirmation"=>"kc", "roles"=>"guest", "password"=>"kc", "email"=>"kc#one.com"}
can anyone tell me what's wrong? why the validation wont' pass?
Is it possible that you've got attr_accessible attributes on the user to prevent mass assignment of certain attributes and that :roles isn't in there? You would get a warning about this in your logs though. The default User class generated by restful_authentication does include the attr_accessible call so it may be there without you having added it if you are using that authentication plugin too.
Is there definitely a roles attribute of the right type for users? It looks like you're expecting roles to be a single string from your form but in the code from declarative_authorization you've got (roles || []).map which suggests that that part of the code at least is expecting an array of roles.