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
Related
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
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!
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
I have 3 models with a has_many through relationship: Food (eg: Chocolate), Sub (Chocolate food substitute), Joint (joint table).
Say #food = Food.find(1); The has_many through relationship allows me to do #subs = #food.subs which return all substitutes associated with #food. This work fine, however only the Sub id is saved and not its attributes which are :name and :description as you can see it returned nil when trying to save #food.subs in my create action in my controller:
=> #<ActiveRecord::Associations::CollectionProxy [#<Sub id: 28,name:nil,description:nil,created_at:
"2015-01-07 00:40:35", updated_at: "2015-01-07 00:40:35">]>
I guess the issue lies with my create action in my food controller and perhaps something to do with my nested form as well. I spent countless hours trying to figure this out I am so desperate to find an answer. I really do not know where to look anymore.
I am new to rails so thanks a lot for your help and your time, I really appreciate it. Please if possible adapt your answer to my beginner level :-) .
Down below are samples of my controller, form and relevant information.
Here are my models:
class Food < ActiveRecord::Base
has_many :joints
has_many :subs, :through => :joints
accepts_nested_attributes_for :subs
end
class Sub < ActiveRecord::Base
has_many :joints
has_many :foods, :through => :joints
accepts_nested_attributes_for :foods
end
class Joint < ActiveRecord::Base
belongs_to :food
belongs_to :sub
end
Here is my db-schema FYI:
create_table "foods", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "joints", force: true do |t|
t.integer "food_id"
t.integer "sub_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subs", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Here is my foods_controller:
def new
#food = Food.new
#sub = Sub.new
end
def create
#food = Food.new(food_params)
#food.subs.build(params[:subs])
#food.save
respond_to do |format|
if #food.save
format.html { redirect_to #food, notice: 'Food was successfully created.' }
format.json { render :show, status: :created, location: #food }
else
format.html { render :new }
format.json { render json: #food.errors, status: :unprocessable_entity }
end
end
end
private
def food_params
params.require(:food).permit(:name, :description, subs_attributes: [:name, :description])
end
end
Here is my views/foods/_form:
<%= form_for(#food) do |f| %>
<% if #food.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#food.errors.count, "error") %> prohibited this food from being saved:</h2>
<ul>
<% #food.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div>
<%= f.fields_for(#sub) do |sub| %>
<div class="field">
<%= sub.label :name %>
<%= sub.text_field :name %>
</div>
<div class="field">
<%= sub.label :description %>
<%= sub.text_area :description %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My routes in case it helps:
resources :foods
resources :subs
resources :joints
root "foods#index"
Thank you very much !
Antoine.
In your new action:
def new
#food = Food.new
#food.subs.build
end
and in your view:
<%= f.fields_for :subs do |sub| %>
When you're passing directly an object, this object becomes the new form_builder's object - rails have no idea it is in any way connected with original object so it will result in different field names.
When you pass a symbol, rails will first try to find if your current object defines subs_attributes method. If so it will loop over subs association and build the fields for each associated model.
Reference here.
UPDATE - answer to comment:
Firstly - #subs is not a symbol, it is an instance variable. Symbols start with a colon like :subs. When fields_for receives an argument, it checks whether it is a symbol or object. In former case it search an object associated with form builder (f.object) to find out if it defines <passed_symbol>_attributes=. That way it knows that the model accepts nested attributes for this association so it can behave accordingly (the new form builder is created for each associated object with a correct name - <symbol>_attributes).
When object is passed, rails has no way of detecting if this is in ay way connected to the current object - you could have two associations for the same type of objects, or even it might have absolutely nothing to do with the original object. In that case fields_for acts like it was a nested form_for - resulting form builder will carry the model name of the object (f.object.class.model_name.singular)
i can't insert to my database whats is my problem?
it's bowling game and i have two tables with name "Player" and "Result"
view
<%= form_for player_new_path(#player) do |f|%>
<div class="text_field">
<p>
<%= f.label "Spelare namn" %>
<%= f.text_field :name %>
</p>
<p>
<%= f.submit "Lägg till en spelare"%>
</p>
</div>
Controller
def create
#player = Player.new(params[:players])
if #player.save
redirect_to players_new_path
else
render :action => "new"
end
end
Not work :/
my model:
class Player < ActiveRecord::Base # attr_accessible :title, :body
belongs_to :result
end
and my migrations:
class CreatePlayers < ActiveRecord::Migration
def change
create_table :players do |t|
t.string "name"
t.references :results
t.timestamps
end
Check your params hash. I bet the key isn't 'players', it's probably 'player'.
#player = Player.new(params[:players]) should probably be #player = Player.new(params[:player]) (You are getting a single player as a param)
Otherwise, what error are you getting