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
Related
I have a rails app that has building and floors models
class Building < ApplicationRecord
has_many :floors
end
class Floor < ApplicationRecord
belongs_to :building
end
In my building form I want to ask the user how many floors the building has, and then when the building is created I want to add that many floors.
So the form would look like so:
<%= form_with(model: building, local: true) do |form| %>
<% if building.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(building.errors.count, "error") %> prohibited this building from being saved:</h2>
<ul>
<% building.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :" How many floors does the building have" %>
<%= form.number :floors %> * not sure how to do this
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Note I don't want the number of floors saved on the building model, it just creates the number of floors the user specified.
So if I created a building called "Walts Place" and said it has 10 floors it would create: Walts Place with id:1, and 10 floors with the building_id of 1.
Does that make sense?
Your help is greatly appreciated.
Update:
ActiveRecord::Schema.define(version: 2019_07_30_093037) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "buildings", force: :cascade do |t|
t.string "name"
t.float "distancetocondensors"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "floors", force: :cascade do |t|
t.bigint "building_id", null: false
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["building_id"], name: "index_floors_on_building_id"
end
add_foreign_key "floors", "buildings"
end
you can modify it in your building controller in create action.
def create
#building = Building.new(building_params)
if #building.save
floors = params[:number].to_i
floors.times do
Floor.create(building: #building)
end
redirect_to building_path
else
redirect_to error
end
end
in your form add a field for number of floor without erb
<%= form_with(model: building, local: true) do |form| %>
<% if building.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(building.errors.count, "error") %> prohibited this building
from being saved:</h2>
<ul>
<% building.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<label> How many floors does the building have</label>
<input type="number" name="number"/>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
You can use nested attributes to help you here.
First, in your Building model add:
has_many :floors
accepts_nested_attributes_for :floors
Then in your BuildingController's create action do:
class BuildingController < ApplicationController
...
def create
floors_amount = params[:building][:floors] || 0
building_params = params.require(:building).permit(:name).merge({
# just fill floors array with empty hashes since `id` will be added automatically
floors_attributes: (1..floors_amount).to_a.fill({})
})
#building = Building.create(building_params)
if #buildnig.errors
render :new # show the form page with an error
else
redirect_to #building # or whenever you want to redirect
end
end
...
end
The advantage here is that it wraps the creation of building and floors into a transaction so if something goes wrong it will rollback all changes. I.e. there won't be a case where new Building is inserted into DB but floors didn't due to some error during their creation.
Another convenience here is that if some validation error appears either on building or on any of the floor then it will be set into building.errors. Means you can easily do render :new in the case of errors to display them.
Note:
I see that in your form view you access Building instance as building. So I'm not really sure how you pass it to this view.
In my example I saved building into #building variable so that in this view form you will have to access it as #building. I believe in your new action in BuildingController you should set it to #building as well:
class BuildingController < ApplicationController
...
def new
#building = Building.new
end
...
end
And in your view you will access it as #building then, not building.
Hope, that makes sense
I'm using Closure tree idea in my new website.
In one of the show views I want to select data by the id (1-level descendant) or if the id is null the first level.
How do I connect sql with set result?
The Query:
select id,name
from tags t
join tag_hierarchies th (t.id = th.ancestor_id)
where t.id=nvl(?,0) and th.generations=1
Code so far(problem on app/views/show.erb):
db/schema.rb:
create_table "tags" do |t|
t.string "name", :null=>false
t.boolean "isCat", :default => true
end
create_table "tag_hierarchies", :id => false do |t|
t.integer "ancestor_id", :null => true
t.integer "descendant_id", :null => false
t.integer "generations", :null => false
end
add_foreign_key(:tag_hierarchies, :tags, :column => 'ancestor_id')
add_foreign_key(:tag_hierarchies, :tags, :column => 'descendant_id')
app/models/tag.rb
class Tag < ActiveRecord::Base
#attr_accessible :name, :isCat
validates :name, uniqueness: false, allow_blank: false
end
app/models/Tag_Hierarchie.rb
class TagHierarchie < ActiveRecord::Base
#attr_accessible :ancestor_id, :descendant_id, :generations
end
app/views/show.erb
<% provide(:title, category_name_or_constant(#tags)) %>
<h1><%= category_name_or_constant(#tags)%></h1>
<div class="row">
<div class="span6 offset3">
<%= for(<<here goes the sql by the Closure tree >>) do |f| %>
<%= link_to tag.name, tag %>
<% end %>
</div>
</div>
Add static method (or scope, if you prefer) to your Tag model:
app/models/tag_hierarchy.rb
class TagHierarchy
belongs_to :tag, foreign_key: :ancestor_id
end
app/models/tag.rb
class Tag
has_many :tag_hierarchies, foreign_key: :ancestor_id
def self.descendants(id = nil)
id ||= 0
self.where(id: id).joins(:tag_hierarchies).where(tag_hierarchies: {generations: 1})
end
end
Create a controller:
rake g controller TagsController
Add code to your controller:
app/controllers/tags_controller.rb
class TagsController < ApplicationController
def index
#descendants = Tag.descendants
end
def show
#descendants = Tag.descendants(params[:id])
end
end
Then use all the stuff in your views:
app/views/tags/show.html.erb:
<div class="row">
<div class="span6 offset3">
<%= #descendants.each do |tag| %>
<%= link_to tag.name, tag %>
<%# you can also use tag.tag_hierarchies here %>
<% end %>
</div>
</div>
I suggest you read some tutorials and/or docs on Rails:
Active Record Associations
Rails Routing from the Outside In
Getting Started with Rails
I am trying to create a profile page where I can input "born_on". It will be using the
class CreateWineMakers < ActiveRecord::Migration
def change
create_table :wine_makers do |t|
t.string :name
t.date :born_on
t.text :nationality
t.text :profile
t.text :wine
t.integer :wine_list_id
t.timestamps
end
add_index :wine_makers, :wine_list_id
end
end
Here is my view file.
<%= simple_form_for WineMaker.new do |f| %>
<%= f.input :name %>
<%= f.input :profile %>
<%= f.input :wine %>
<%= f.input :born_on %>
<br/>
<%= f.submit "Create", :class => 'btn btn-primary' %>
<% end %>
The "born_on" is giving me error saying the method is not defined. I am confused since all other inputs are working except "born_on" and "nationality". Before, my "born_on" was named "birth_date", and I thought the naming convention was wrong and changed it to "born_on". Here is the controller.
class WineMakersController < ApplicationController
def new
#wine_maker = WineMaker.new
end
def create
#wine_maker = WineMaker.create(wine_maker_params)
redirect_to wine_list_path(#wine_list)
end
def show
end
private
def wine_maker_params
params.require(:wine_maker).permit(:name, :born_on, :nationality, :profile, :wine )
end
end
This seems like such an easy question that I couldn't find similar problems..
Thank you.
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