Hello I want to write a small blog with Ruby on Rails (3), with posts and comments submitted via a ajax form.
But when I submit a comment it is often shown twice, and I got no idea why.
when I write #post.comments.uniq in the _create.js.rjs file, it works fine but this seems not to be a clean solution.
When I reload the page without ajax after inserting a comment the comment is also not shown twice. Only when I insert it via ajax.
Here is the sourcecode of my project.
Blog::Application.routes.draw do
root :to => 'posts#index'
resources :posts do
resources :comments
end
end
config/routes.rb
ActiveRecord::Schema.define(:version => 20100907105618) do
create_table "comments", :force => true do |t|
t.text "text"
t.integer "post_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "posts", :force => true do |t|
t.string "title"
t.text "text"
t.datetime "created_at"
t.datetime "updated_at"
end
end
db/schema.rb
class Comment < ActiveRecord::Base
belongs_to :post
default_scope :order => "id DESC"
end
app/models/comment.rb
class Post < ActiveRecord::Base
has_many :comments
end
app/models/post.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
end
end
app/controllers/posts_controller.rb
class CommentsController < ApplicationController
respond_to :js
def create
#post = Post.find(params[:post_id])
# if I write here p #post.comments.inspect
# it shows that there where 2 comments with the same id, how could this be?
#post.comments.create(params[:comment])
end
end
app/controllers/comments_controller.rb
<h2><%= #post.title %></h2>
<p>
<%= #post.text %>
</p>
<%= form_for [#post, Comment.new], :remote => true do |f| %>
<%= f.text_area :text, :rows => 4 %><br />
<%= f.submit "send" %>
<% end %>
<div id="comments_box">
<% if #post.comments.any? %>
<%= render :partial => #post.comments %>
<% else %>
No Comments yet
<% end %>
</div>
app/views/posts/show.html.erb
<div id="comment_<%= comment.id %>"><%= comment.text %></div>
app/views/comments/_comment.html.erb
page[:comment_text].clear
page[:comments_box].replace_html :partial => #post.comments
# ^ write here #post.comments.uniq it works
page.visual_effect(:highlight, "comment_#{#post.comments.first.id}")
app/views/comments/create.js.rjs
<% #posts.each do |post| %>
<%= link_to post.title, post %>
<% end %>
app/views/posts/index.html.erb
EDIT:
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
app/views/layouts/application.html.erb
I'm still not a profi in rails but you can check which js you link to your application layout file. I used to link once defaults and after my application.js and received all the ajax action twice. However I'm not sure about your case. The code you pasted looks fine.
I believe what's happening here is...
when you call
#post.comments.create(params[:comment])
Rails appends a new comment to the post. Then, when calling
:partial => #post.comments
Rails will call all of the comments from the DB that belong to this post.
We can see this in the log:
SQL (0.5ms) INSERT INTO "comments" ("created_at", "post_id", "text", "updated_at") VALUES ('2010-09-09 11:10:18.874471', 1, 'lots of pies', '2010-09-09 11:10:18.874471')
Comment Load (0.9ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id = 1) ORDER BY id DESC
Instead, try creating a new comment and then saving like so:
class CommentsController < ApplicationController
respond_to :js
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
#comment.save
end
end
And in the view:
page[:comment_text].clear
page[:comments_box].replace_html render(#post.comments)
page.visual_effect(:highlight, "comment_#{#post.comments.first.id}")
I've posted this as an example to Github
http://github.com/GavinM/Comments-Demo
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
I am trying to create a basic Item creation in rails but I am having trouble creating new item. I want to create item's name, say Wash the dishes. These are the codes that I have:
Routes:
resources :items
ItemsController:
class ItemsController < ApplicationController
...
def new
#item = Item.new
end
def create
#item = Item.new(item_params)
if #item.save
flash[:notice] = "Item was saved!"
redirect_to #item
else
flash.now[:alert] = "ERROR. ERROR."
render :new
end
end
...
private
def item_params
params.require(:item).permit(:name, :list_id)
end
end
items/new.html.erb
<%= form_for :item do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
Lastly, schema:
create_table "items", force: :cascade do |t|
t.string "name"
t.integer "list_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
...
I got several different error codes, but this is the one I am currently stuck at (other times it showed different error code or it would simply prints out "ERROR. ERROR." (alert that I setup when save fails)
Routing Error
No route matches [POST] "/items/new"
When I go to rake routes:
POST /items(.:format) items#create
new_item GET /items/new(.:format) items#new
I followed suggestion from this SO post to check my routes and this is what I have:
2.2.2 :019 > r = Rails.application.routes
=> #<ActionDispatch::Routing::RouteSet:0x007fff323c3230>
2.2.2 :020 > r.recognize_path "/items/new"
=> {:controller=>"items", :action=>"new"}
I have also gone to rails c and I was able to create new item manually. (i = Item.new(name:"Test 123"); i.save)
What did I miss?
The problem is with your form. To understand what's wrong, do the following:
Start the rails server using rails s
Go to http://localhost:3000/items/new
Instead of filling in the form fields, view the source page
Check the form tag. Its submitting the form data to /items/new. ie.the action attribute is set to /items/new. Why is that?
From the documentation:
When the model is represented by a string or symbol, if the :url option is not specified, by default the form will be sent back to the current url (We will describe below an alternative resource-oriented usage of form_for in which the URL does not need to be specified explicitly).
<form action="/items/new" accept-charset="UTF-8" method="post">
In your routes.rb, there's no route matching POST /items/new
So, modify your form to
<%= form_for :item, url: items_path do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
This generates a form tag which posts the data to /itemsrather than /items/new.
Or replace your form with
<%= form_for #item do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
Now, the form will be submitted to /items. The advantage of using the second version is you can dry out your form for creating a new object and updating an existing object into a single view(partial).
For more, see http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object
Try this in items/new.html.erb
<%= form_for #item do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
https://gist.github.com/Gtar69/27139cee82c75992ed82
Mac OS 10.9.3
Rails 4.1.0
Now I want to add photo upload to my website ! In that way, in terminal=>
$ rails g model Photo image_name:string product:references
$ rake db:migrate
$ rails g migration add_image_to_photos image:string
$ rake db:migrate
$ rails g controller Photos
Scheme as below:
ActiveRecord::Schema.define(version: 20140612034245) do
create_table "photos", force: true do |t|
t.string "image_name"
t.integer "product_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "image"
end
config/routes.rb
Rails.application.routes.draw do
devise_for :users
namespace :admin do
resources :products do
resources :photos
end
end
end
For new created controller, PhotosController shown below"
class PhotosController < ApplicationController
def create
#product = Product.find(params[:product_id])
#photo = #product.photos.create(photo_params)
redirect_to admin_product_path(#product)
end
private
def photo_params
params.require(:photo).permit(:image_name, :iamge)
end
end
Finally, adding "upload photos feature" in new.html.erb
<div class="panel panel-default">
<div class="panel-body">
<%= simple_form_for [:admin, #product] do|f| %>
<div class="group">
<%= f.input :title, label:'ζ¨ι‘' %>
</div>
<div class="group">
<%= f.input :description, label:'ζθΏ°'%>
</div>
<div class = "group">
<%= f.input :quantity, label:'ζΈι' %>
</div>
<h2>Add Photos</h2>
<%= form_for([#product, #product.photos.build]) do |f| %>
<% end %>
<%= f.submit "Submit", :disable_with => 'Submiting...' %>
</div>
</div>
However, I encounter the problem I can't understand =>
Rendered admin/products/new.html.erb within layouts/application (18.8ms)
Completed 500 Internal Server Error in 57ms
Showing /Users/Gtar/projects/artstore/app/views/admin/products/new.html.erb where line #26 raised:
undefined method `product_photos_path' for #<#<Class:0x000001013b2ef8>:0x00000101118670>
ActionView::Template::Error (undefined method `product_photos_path' for #<#<Class:0x000001013b2ef8>:0x00000101118670>):
23: </div> form_for([#article, #article.comments.build]) do |f| %>
24: -->
25: <h2>Add Photos</h2>
26: <%= form_for([#product, #product.photos.build]) do |f| %>
27: <% end %>
28: <%= f.submit "Submit", :disable_with => 'Submiting...' %>
29: </div>
app/views/admin/products/new.html.erb:26:in `block in _app_views_admin_products_new_html_erb__2984465001892677316_2165881840'
app/views/admin/products/new.html.erb:8:in `_app_views_admin_products_new_html_erb__2984465001892677316_2165881840'
Would you mind helping me for solve the issure?
Routes
For reference for future readers, here's your error:
undefined method `product_photos_path'
--
The problem here, as stated in the comments, is you're referencing a nested route, which doesn't exist. You'll need to do something like this in your routes:
#config/routes.rb
#no namespace
resources :products do
resources :photos #-> domain.com/products/1/photos
end
This will give you the route you need. You're currently using a namespace, which means you'll have to reference something like admin_product_photos_path
Paperclip
Something else you should consider is paperclip
This is a gem which helps you upload & save files to your database. It works much the same as you have now, except it uses its own datatable structure, with a call in your model to define the object:
#app/models/photo.rb
Class Photo < ActiveRecord::Base
has_attached_file :image
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
This will allow you to call the image object like this:
#product = Product.find params[:id]
#product.photos.first.image #-> image object from Paperclip
If you'd like more information, I would recommend you use this Railscast:
I've tried following this advice but I haven't succeeded yet in generating a form containing 3 objects of the same type under one submit button.
When I navigate to a page that should show a form containing fields for 3 objects (called elements in this example) I get the following error:
undefined method 'elements' for nil:NilClass
Any pointers would be much appreciated! My code is as follows:
app/controllers/elements_controller.rb
class ElementsController < ApplicationController
def index
#element_group = ElementGroup.new
render 'pages/index'
end
end
app/views/pages/home.html.erb
<%= render 'element_groups/form'%>
app/views/element_groups/_form.html.erb
<% form_for :element_group do |f|%>
## The error comes from this next line, as f.object is nil
<% f.object.elements.each do |element| %>
<% f.fields_for element do |element_form| %>
<%= element_form.text_field :content %>
<%= element_form.text_field :element_type %>
<%= element_form.text_field :subtype %>
<% end %>
<% end %>
<% end %>
app/models/element_group.rb
class ElementGroup
attr_accessor :elements
def elements
#elements = []
3.times do
#elements << Element.new
end
#elements
end
end
app/models/element.rb
class Element < ActiveRecord::Base
attr_accessible :element_type, :subtype, :content
end
db/schema.rb
create_table "elements", :force => true do |t|
t.string "element_type"
t.string "subtype"
t.string "content"
t.datetime "created_at"
t.datetime "updated_at"
end
Have you tried to change to <% form_for #element_group do |f|%> ?
I am just trying to print the parameters that have been entered into my form.
Basically I create a new bet then I display the parameters:
MIGRATION
class CreateBets < ActiveRecord::Migration
def self.up
create_table :bets do |t|
t.integer :accepted ,:default => 0
t.integer :user_1_id #proposer
t.integer :user_2_id #receiver
t.integer :team_1_id #proposer's team
t.integer :team_2_id #receiver's team
t.integer :game_id
t.integer :winner
t.integer :amount
t.timestamps
end
end
def self.down
drop_table :bets
end
end
CONTROLLER
bets_controller.erb
class BetsController < ApplicationController
def index
redirect_to new_bet_path
end
def new
#b=Bet.new
end
def create
#points=params[:points]
#winner=params[:winner]
end
end
VIEWS
New.erb
<% facebook_form_for Bet.new do |f| %>
<%= f.text_field :amount, :label=>"points" %>
<%= f.text_field :winner, :label=>"WinningTeam" %>
<%= f.buttons "Bet" %>
<% end %>
create.erb
points:<%= #points %>
<br>
winner:<%= #winner %>
I tried to make this code work with instance variables but it didn't work either. Where is the problem?
Thank you.
I think that params[:winner] and params[:point] is empty hash. Try adding this to your create.erb:
params: <%= params.inspect %>
It will display your params hash, so you will see how to get to it.
Another hint, why you are creating new object in new action and then in form you are doing it again? So:
<% facebook_form_for #b do |f| %>
And another thing, it is really good to keep naming conventions, so don't create #b object, but #bet.
In create action you should have line like this:
#bet = Bet.new(params[:bet])
And in view:
<p>
points:<%= #bet.points %>
</p>
<p>
winner:<%= #bet.winner %>
</p>
If you use <br> it's better to use <br/>.
Your index action is totaly useless. It would be better if you would move all behaviour from new action to index and remove new action completly.
As klew pointed, for me it seems that you're getting empty params[:winner]and params[:point]. You can make sure that of what you're getting by taking a look at your servers log.
You will see a line like
Processing BetsController#create (for 127.0.0.1 at 2010-04-11 20:56:51) [POST]
Parameters: {"your"=>"parameters", "should"=>"apper in this hash"}