This is an extension of this original question: Rails - Editing User and Profile Models from separate Settings Controller
My form works perfectly for editing a single model (Profile), however I have attempted to extend this to also allow for a user to edit some of the fields from the User model. Currently, the entire form is no longer saving any data - but I am not seeing any visible error messages in browser, other than the "success" message in my update method is not firing.
How can I successfully extend this setup to allow for both User and Profile fields to be saved in the same form? The form currently edits a Profile, and then allows fields_for a user - is this the wrong way around?
I have 2 models, User:
class User < ApplicationRecord
has_one :profile, dependent: :destroy
before_create :create_profile
private
def create_profile
build_profile(name: username)
end
end
and Profile:
class Profile < ApplicationRecord
belongs_to :user
accepts_nested_attributes_for :user
end
Both models are editable via the SettingsController:
class SettingsController < ApplicationController
def profile
#profile = User.find_by_id(current_user).profile
end
def update
set_profile
respond_to do |format|
if #profile.update(profile_params)
format.html { redirect_back fallback_location: settings_path, notice: 'Profile was successfully updated.' }
else
format.html { render :edit }
end
end
end
private
def profile_params
params.require(:profile).permit(:name, user_attributes: [:email])
end
end
On settings/profile, a user's profile is editable with the following form:
<h1>Settings</h1>
<div>
<div>
Name: <%= #profile.name %>
</div>
<%= form_with(model: #profile, url: update_settings_profile_path, local: true) do |form| %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<%= form.fields_for :user do |user_form| %>
<div class="field">
<%= user_form.label :email %>
<%= user_form.text_field :email %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
</div>
Are here are the list of routes that show the profile page, and the update method for all of the other methods:
get 'settings', to: redirect('settings/profile')
get 'settings/profile', to: 'settings#profile', as: :settings_profile
patch 'settings', to: 'settings#update', as: :update_settings
Parameters when the form is submitted: (Removed auth token for clarity.)
Parameters: {"utf8"=>"✓", "authenticity_token"=>"X", "profile"=>{"name"=>"John Doe", "user_attributes"=>{"email"=>"test#email.com", "id"=>"22"}}, "commit"=>"Update Profile"}
Schema: (Basic columns removed for clarity.)
create_table "profiles", force: :cascade do |t|
t.string "name"
t.bigint "user_id"
...
t.index ["user_id"], name: "index_profiles_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "username", default: "", null: false
...
end
Appreciate any tips!
Related
I'm doing a role permission edit function. It didn't have error but the logic of the code have some problem.
It didn't update the permission but update whole role_permission so it came out like image 1 & 2, it keep update many times.
I need to get the worker role_permission then check if that the permission already have?, if have then no need to add, if no then need to add(update). How can i check it at role controller update there?
Role controller
def edit
#role = Role.find(params[:id])
#role.company_id = params[:company_id]
#permissions = Permission.all
end
def update
#role = Role.find(params[:id])
#company_id = Company.find(params[:role][:company_id])
if #role.update!(role_params)
permission_ids = params[:permission_ids]
permission_ids.each do |permission_id|
RolePermission.update(role_id: #role.id, permission_id: permission_id)
end
flash[:success] = "Profile updated"
redirect_to #role
else
render 'edit'
end
end
Edit.html.erb
<% provide(:title, "Edit Roles") %>
<h1 class="dashboard">Update Role</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: #role, local: true) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= #permissions.each do |permission|%>
<%= check_box_tag 'permission_ids[]', permission.id%>
<%= f.label :permission_name, permission.name %>
<% end %>
<%= f.hidden_field :company_id , value: 2%>
<%= f.submit "Save changes", class: "btn btn-secondary bottom" %>
<% end %>
</div>
</div>
RolePermission migration table
create_table "role_permissions", force: :cascade do |t|
t.integer "role_id"
t.integer "permission_id"
t.integer "company_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
parameter pass in console
parameter pass in console
Update
roles controller (role_params)
def role_params
params.require(:role).permit(:name, :company_id)
end
console logs when update role
console log
console log
Hi since you want Update/Add permissions related to roles.
can do it with the help of first_or_initialize:
def update
#role = Role.find(params[:id])
#company_id = Company.find(params[:role][:company_id])
if #role.update!(role_params)
permission_ids = params[:permission_ids]
permission_ids.each do |permission_id|
role_permissions = #role.role_permissions.where(permission_id: permission_id).first_or_initialize
role_permissions.save
end
flash[:success] = "Profile updated"
redirect_to #role
else
render 'edit'
end
end
Read more about first_or_initialize
OR
you should have the following associations with the below models:
# role.rb
has_many :role_permissions, dependent: :destroy
has_many :permissions, through: :role_permissions, source: :permission
# role_permission.rb
belongs_to :role
belongs_to :permission
Then in your roles_controller.rb should have below:
def update
#role = Role.find(params[:id])
#company_id = Company.find(params[:role][:company_id])
if #role.update!(role_params)
flash[:success] = "Profile updated"
redirect_to #role
else
render 'edit'
end
end
def role_params
params.require(:role).permit(:name, :company_id, permission_ids: [])
end
Description
I am trying to create messages based on selected (via check box) users from the browser in Ruby on Rails.
Snapshot:
Steps to reproduce
My schema
ActiveRecord::Schema.define(version: 2021_11_13_142255) do
create_table "messages", force: :cascade do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "role"
t.integer "phone"
t.boolean "admin"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
messages_controller.rb
class MessagesController < ApplicationController
def new
#users = User.all
#message = Message.new(message_params)
end
def create
params[:user_objs].each do |u|
# "params.inspect" returns
# {"authenticity_token"=>"[FILTERED]",
# "user_objs"=>
# ["{\"id\":1,\"name\":\"Alex\",\"role\":\"Engineer\",\"phone\":998943333303,\"admin\":true,\"created_at\":\"2021-11-13T14:37:54.962Z\",\"updated_at\":\"2021-11-13T14:37:54.962Z\"}",
# "{\"id\":2,\"name\":\"Lucy\",\"role\":\"Accountant\",\"phone\":998943333303,\"admin\":false,\"created_at\":\"2021-11-13T14:39:52.742Z\",\"updated_at\":\"2021-11-13T14:39:52.742Z\"}"],
# "message"=>{"content"=>"Message from the browser"},
# "commit"=>"Send"}
person = JSON.parse(u)
#message = person.messages.new(message_params)
if #message.save
redirect_to root_path
else
#users = User.all
render :new
end
end
end
private
def message_params
params.permit(
:content,
:user_id
)
end
end
messages => new.html.erb
<div>
<h1>Create and send a new message!</h1>
<%= form_for(#message) do |form| %>
<% if #message.errors.any? %>
<div class="alert alert-danger">
<h5 class="fw-bold">Invalid input!</h5>
<%= #message.errors.full_messages.each do |error| %>
<div><%= error %></div>
<% end %>
</div>
<% end %>
<% #users.each do |u| %>
<div>
<p><%= check_box_tag "user_objs[]", u.to_json %> <%= u.name %></p>
</div>
<% end %>
<p class="mb-3">
<%= form.label :content, class: "form-label" %>
<%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
</p>
<p class="mb-3">
<%= form.submit "Send", class: "btn btn-primary" %>
</p>
<% end %>
</div>
<%= params.inspect %>
Models
# user.rb
class User < ApplicationRecord
has_many :messages
end
# message.rb
class Message < ApplicationRecord
belongs_to :user
end
Expected behavior
I was expecting the creation of messages for all selected users
Actual behavior
NoMethodError in MessagesController#create
undefined method `messages' for #<Hash:0x000000011fe2b420>
I tried different ways, but can't convert Ruby objects to JSON in my params user_objs[] so that I can parse it in my controller to create messages based on those selected users in the user_objs[] params.
Environment info
ruby -v
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [arm64-darwin20]
rails -v
Rails 6.1.4.1
Thanks for any given help 🙏
If you want to create a system where you send a single message to multiple users you would setup a join table:
class User < ApplicationRecord
has_many :user_messages
has_many :recieved_messages, though: :user_messages,
source: :message,
inverse_of: :recipients
end
# rails g model user_message user:belongs_to message:belongs_to read:boolean
class UserMessage < ApplicationRecord
belongs_to :user
belongs_to :message
# make sure to add a compound unique index to the migration as well
validates_uniqueness_of :user_id, scope: :message_id
delegate :content, to: :message
end
class Message < ApplicationRecord
has_many :user_messages
has_many :recipients, though: :user_messages,
source: :user,
inverse_of: :recieved_messages
end
has_many :recipients will create a recipient_ids= setter and a recipient_ids getter that you can use in your form:
<div>
<h1>Create and send a new message!</h1>
<%= form_with(model: #message) do |form| %>
<% if #message.errors.any? %>
<div class="alert alert-danger">
<h5 class="fw-bold">Invalid input!</h5>
<%= #message.errors.full_messages.each do |error| %>
<div><%= error %></div>
<% end %>
</div>
<% end %>
<p class="mb-3">
<%= form.collection_checkboxes(:recipient_ids, #users, :id, :name) %>
</p>
<p class="mb-3">
<%= form.label :content, class: "form-label" %>
<%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
</p>
<p class="mb-3">
<%= form.submit "Send", class: "btn btn-primary" %>
</p>
<% end %>
</div>
There is absolutely no need to pass the entire record as JSON - you just pass an array of IDs and rails will do all the work of creating the join table rows for you:
class MessagesController < ApplicationController
def new
#users = User.all
#message = Message.new
end
def create
#message = Message.new(message_params)
if #message.save
redirect_to root_path
else
#users = User.all
render :new
end
end
private
def message_params
params.require(:message)
.permit(
:content,
recipient_ids: []
)
end
end
This avoids the complexity of creating multiple records from a single request and the whole conundrum that you're binding the form to a single instance of Message but creating a bunch of records which is bound to lead to confusion.
If you want to create multiple records at once it can be done but the complexity is far higher and you have to deal with stuff like how to handle errors if creating one message fails and this might be beyond your current skill level.
The issue is that you are assigning a json object/hash in person = JSON.parse(u). This is not an active record so when doing person.messages it throws the error. I believe what you need in the create action is something like:
user = JSON.parse(u)
# make sure user.inspect gives you the user object you want
person = User.find(user["id"])
# person.inspect should give you the active record for the user
Im building an app where users can publish there Real Estate properties. So I have two tables one that is called Property and the other called Amenity(for icons likes bathrooms, pool, and etc.) I made the Amenity table separated from the Property table so I can use it with other tables and I have this error Unpermitted parameter::gym
So this is my code:
property.rb model
class Property < ApplicationRecord
belongs_to :owner
has_many :amenities
accepts_nested_attributes_for :amenities
end
amenity.rb model
class Amenity < ApplicationRecord
belongs_to :property
end
properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_property, only: [:show, :edit, :update, :destroy]
before_action :authenticate_owner!
.
.
.
# POST /properties
# POST /properties.json
def create
#property = current_owner.properties.new(property_params)
respond_to do |format|
if #property.save
format.html { redirect_to #property, notice: 'Tu propiedad ha sido creada!' }
format.json { render :show, status: :created, location: #property }
else
format.html { render :new }
format.json { render json: #property.errors, status: :unprocessable_entity }
end
end
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_property
#property = Property.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def property_params
params.require(:property).permit(:name, :description, :price, amenities_attributes: [:id, :bathroom, :room, :pool, :gym,
:kitchen, :terrace, :balcony, :living_room, :garage, :parking_lot, :green_area])
end
end
amenities migration table
class CreateAmenities < ActiveRecord::Migration[5.2]
def change
create_table :amenities do |t|
t.integer :bathroom
t.integer :room
t.integer :pool
t.integer :gym
t.integer :kitchen
t.integer :terrace
t.integer :balcony
t.integer :living_room
t.integer :garage
t.integer :parking_lot
t.integer :green_areas
t.references :property
t.timestamps
end
add_index :amenities, [:id, :created_at]
end
end
properties migration table
class CreateProperties < ActiveRecord::Migration[5.2]
def change
create_table :properties do |t|
t.string :name
t.text :description
t.integer :price
t.string :services
t.string :rules
t.string :address
t.float :latitude
t.float :longitude
t.references :owner
t.timestamps
end
add_index :properties, [:id, :rfc, :created_at]
end
end
Console logs
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GjmTFKS3cQRwgrSnTLFOoWQV/gXdTgST0nf7GOs7ZS2i8wneFqzADeTLUo26UKkA5392nrDKGZpVyav4LWpfjw==", "property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "gym"=>"1"}, "commit"=>"Create Property"}
Owner Load (0.3ms) SELECT "owners".* FROM "owners" WHERE "owners"."id" = ? ORDER BY "owners"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ /Users/kensanchez/.rvm/gems/ruby-2.5.3/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
Unpermitted parameter: :gym
As im concerned this has to work fine, but im having some issues understanding it. I will appreciate your help guys! Thanks.
EDIT:
My web form
<%= form_with(model: property, local: true) do |form| %>
<% if property.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(property.errors.count, "error") %> prohibited this property from being saved:</h2>
<ul>
<% property.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="container">
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
.
.
.
<!--Gym attribute from amenities-->
<div class="field">
<%= form.label :gym %>
<%= form.number_field :gym %>
</div>
<div class="actions">
<%= form.submit %>
</div>
</div>
<% end %>
This part of your parameters "property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "gym"=>"1"} should has the same structure as it is in property_params.
The parameter gym has to be inside amenities_attributes.
Like this: "property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "amenities_attributes" => [{ "gym"=>"1" }]}.
UPD
Check this out https://guides.rubyonrails.org/form_helpers.html#nested-forms
Try to use this piece of code in the form view:
<!--Gym attribute from amenities-->
<%= form.fields_for :amenities do |amenities_form| %>
<div class="field">
<%= amenities_form.label :gym %>
<%= amenities_form.number_field :gym %>
</div>
<% end %>
This what I am seeing in your console logs output
"property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "gym"=>"1"}
These are params for a property and the last value is "gym"=>"1", this is the reason you are getting unpermitted parameter.
It should appear under amenities_attributes like
"property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000"}, "amenities_attributes": [{ "gym"=>"1" }] }
I'm trying to design a complicated form. Fields also need to add data that I received with queries to other tables.
Here's what I'm trying to do.
Rad_check form is composed of username and password... When the user registers by typing username and password, the Rad_cheks table must consist of other records in the back. I was able to do some of the Radcheck model. However, I want to conditionally query the tenant_id column in the Nas table and insert it into the Rad_checks table. I've actually prepared a query for that, but I don't know how to use it.
Na.select(:tenant_id).where(Na.arel_table[:realipaddr].eq('form's real ip will be'))
Actually, I'm using the Milia gem file. However, there will be a somewhat more public form... the query I created must come in a way instead of the IP address of the request. REMOTE_IP code. This means that the user's actual IP address is tenant_id information that is equal to the IP address in the NAS table.
When saving the Rad_chek form, I want to query the user's actual IP address in the Nas table and add the tenant_id number of the equal data to the Rad_cheks table. How can I do that?
Help me! Please
TABLE
class CreateRadChecks < ActiveRecord::Migration[5.2]
def change
create_table :rad_checks do |t|
t.integer :tenant_id
t.string :username
t.string :password
t.string :attribu
t.string :op
t.timestamps
end
end
end
FORM
<%= form_with(model: rad_check, local: true) do |form| %>
<% if rad_check.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(rad_check.errors.count, "error") %> prohibited this rad_check from being saved:</h2>
<ul>
<% rad_check.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :username %>
<%= form.text_field :username %>
</div>
<div class="field">
<%= form.label :password %>
<%= form.text_field :password %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
MODEL
class RadCheck < ApplicationRecord
has_one :rad_user_group, dependent: :destroy
after_initialize :add_rad_user_group
before_save :set_radcheck
def add_rad_user_group
self.rad_user_group ||= RadUserGroup.new if self.new_record?
end
def set_radcheck
self.rad_user_group.username = username
self.op = ":="
self.attribu = "Cleartext-Password"
end
end
rad_user_group table
class CreateRadUserGroups < ActiveRecord::Migration[5.2]
def change
create_table :rad_user_groups do |t|
t.integer :tenant_id
t.string :username
t.string :groupname
t.references :rad_check, foreign_key: true
t.timestamps
end
end
end
nas table
class CreateNas < ActiveRecord::Migration[5.2]
def change
create_table :nas do |t|
t.integer :tenant_id
t.string :nasname
t.string :realipaddr
t.boolean :active
end
end
end
Assuming:
You have a variable called realipaddr
Nas belongs_to :tenant
RadCheck belongs_to :tenant
you have instantiated a variable called #rad_check
Then you should be able to do something like:
#rad_check.update(tenant: Nas.find_by(realipaddr: realipaddr).tenant)
Or, if you don't want the save that occurs with update:
#rad_check.tenant = Nas.find_by(realipaddr: realipaddr).tenant
If you're instantiating #rad_check based on rad_check_params in a controller, you might do something like:
def create
#rad_check = RadCheck.new(rad_check_params)
if #rad_check.save
# do something
else
# do something else
end
end
def rad_check_params
params.require(:rad_check).permit(:some, :parameters).merge!(tenant: nas_tenant))
end
def nas_tenant
Nas.find_by(realipaddr: realipaddr).tenant
end
There are probably other ways, too.
That's how it worked. I really appreciate the information you gave me. Only the variable request.remote_ip returns the local IP address. The ISP does not give the IP address. How do I make this?
def realipaddr
request.remote_addr
end
# POST /rad_checks
# POST /rad_checks.json
def create
#rad_check = RadCheck.new(rad_check_params)
#rad_check.tenant_id = Na.find_by(realipaddr: realipaddr).tenant_id
respond_to do |format|
if #rad_check.save
format.html { redirect_to #rad_check, notice: 'Rad check was successfully created.' }
format.json { render :show, status: :created, location: #rad_check }
else
format.html { render :new }
format.json { render json: #rad_check.errors, status: :unprocessable_entity }
end
end
end
I have a Rails 4.2 app which has 'Rooms', 'Bookings' and 'Extras'.
When making a booking it is for a room e.g. website.com/rooms/1/bookings/1
I have extras which I want to be associated with the booking for that room via check-boxes.
How can this be implemented? I've been reading about has_many :foo, :through => :bar associations but I'm not sure if that's the way to go.
The relevant code looks like this:
<!-- app\views\bookings\_form.html.erb -->
<%= form_for([#room, #booking]) do |f| %>
<p>
<%= f.label 'Select Customer:' %>
<%= f.collection_select :user_id, User.all, :id, :customer_name %>
</p>
<p>
<%= f.label 'start_time', 'Start Date and Time:' %>
<%= f.datetime_select :start_time, { minute_step: 15 } %>
</p>
<p>
<%= f.label 'length', 'Length of booking in hours:' %>
<%= f.number_field 'length', min: 1 %>
</p>
<p>
<%= f.label 'Room Price:' %>
<%= number_to_currency #room.price, unit: "£" %>
</p>
<p>
<%= f.label 'Extras:' %>
<%= f.collection_check_boxes :extra_ids, Extra.all, :id, :extra_info %>
</p>
<%= f.submit 'Submit' %>
<% end %>
# app\models\booking.rb
class Booking < ActiveRecord::Base
belongs_to :room
belongs_to :user
has_many :additions
has_many :extras, :through => :additions
end
# app\models\extra.rb
class Extra < ActiveRecord::Base
belongs_to :extracat
has_many :additions
has_many :bookings, :through => :additions
def extra_info
"#{name}"
end
end
# This model is for the has_many through testing I tried
# app\models\addition.rb
class Addition < ActiveRecord::Base
belongs_to :booking
belongs_to :extra
end
# Relevant section of schema
create_table "additions", force: :cascade do |t|
t.integer "booking_id"
t.integer "extra_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "bookings", force: :cascade do |t|
t.datetime "start_time"
t.datetime "end_time"
t.integer "length"
t.integer "room_id"
t.integer "user_id"
t.integer "extra_id"
end
EDIT - The section within the bookings show page.
# app\views\bookings\show.html.erb
<% #booking.extras.each do |e| %>
<%= e.name %>,
<% end %>
EDIT - Adding bookings controller
class BookingsController < ApplicationController
respond_to :html, :xml, :json
before_action :find_room
def index
#bookings = Booking.where("room_id = ? AND end_time >= ?", #room.id, Time.now).order(:start_time)
respond_with #bookings
end
def new
#booking = Booking.new(room_id: #room.id)
end
def create
#booking = Booking.new(params[:booking].permit(:room_id, :start_time, :length))
#booking.room = #room
if #booking.save
redirect_to room_bookings_path(#room, method: :get)
else
render 'new'
end
end
def show
#booking = Booking.find(params[:id])
end
def destroy
#booking = Booking.find(params[:id]).destroy
if #booking.destroy
flash[:notice] = "Booking: #{#booking.start_time.strftime('%e %b %Y %H:%M%p')} to #{#booking.end_time.strftime('%e %b %Y %H:%M%p')} deleted"
redirect_to room_bookings_path(#room)
else
render 'index'
end
end
def edit
#booking = Booking.find(params[:id])
end
def update
#booking = Booking.find(params[:id])
# #booking.room = #room
if #booking.update(params[:booking].permit(:room_id, :start_time, :length))
flash[:notice] = 'Your booking was updated succesfully'
if request.xhr?
render json: {status: :success}.to_json
else
redirect_to resource_bookings_path(#room)
end
else
render 'edit'
end
end
private
def save booking
if #booking.save
flash[:notice] = 'booking added'
redirect_to room_booking_path(#room, #booking)
else
render 'new'
end
end
def find_room
if params[:room_id]
#room = Room.find_by_id(params[:room_id])
end
end
def booking_params
params.require(:booking).permit(:user_id, :extra_id)
end
end
How is it possible to associate the extras with a booking? As so far they are not being saved with the booking into the database. Is this a controller issue?
You're not permitting the parameters correctly - the name is extra_ids. In addition since the parameter is an array you need to permit it like so:
params.require(:booking).permit(:room_id, :start_time, :length, :extra_ids => [])
Personally I recommend setting action controller to raise an error when unpermitted parameters are encountered in development or tests - very easy otherwise to miss the log messages