I am trying to make a user review website. I need to give ownership of a created review to another user. Here is my code
_form,html.erb file
<%= form_with(model: review, local: true) do |form| %>
<% if review.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(review.errors.count, "error") %> prohibited this review from being saved:</h2>
<ul>
<% review.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field-group">
<%= form.label :Review_Title %>
<%= form.select(:title, options_for_select([ ['Excellent Experience','Excellent Experience'], ['Great Experience','Great Experience'], ['Average Experience','Average Experience'], ['Poor Experience','Poor Experience'], ['Terrible Experience','Terrible Experience']])) %>
</div>
<div class="field-group">
<%= form.label :rating %>
<%= form.number_field :rating, min: 1, max:5, class: "form-control" %>
</div>
<div class="field-group">
<%= form.label :"Description"%>
<%= form.text_area :description, class: "form-control" %>
</div>
<div class="field-group">
<%= form.label :reviewer %>
<%= form.text_field :reviewer, class: "form-control", value:current_user.name, :disabled => true %>
</div>
<br>
<div class="field-group">
<%= form.label :"User Reviewing" %>
<%= form.select :user_reviewed, User. where("email <> ?", current_user.email).pluck(:email, :name), :prompt => "Select one", class: "form-control" %>
</div>
<br>
<div class="actions-group">
<%= form.submit "Submit", class: "btn btn-primary"%>
</div>
<% end %>
The create file from the reviews controller
def create
#review = current_user.reviews.new(review_params)
respond_to do |format|
if #review.save
format.html { redirect_to #review, notice: 'Review was successfully created.' }
format.json { render :show, status: :created, location: #review }
else
format.html { render :new }
format.json { render json: #review.errors, status: :unprocessable_entity }
end
end
end
This code gives ownership of the file to the current_user and I am trying to get the information of a different. The email of the user that I want is passed in the form and saved in user_reviewed. Is there a way to replace the current_user code before the reviews.new(review_params)?
# Table name: reviews
#
# id :bigint not null, primary key
# description :text
# rating :integer
# reviewer :string
# title :string
# user_reviewed :string
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint
#
# Indexes
#
# index_reviews_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
Is there a way to replace the current_user code before the reviews.new(review_params)
That's not the right way to look at this.
Your review seems to have two things, which are named:
The person who wrote the review. (reviewer is their name)
The person who "owns" the review. (user_reviewed is their email, and user_id is their ID.)
This is super confusing. (And I'm really not clear what the thing being reviewed actually is???!! Reviews have a title and a description, but is it supposed to be linked to something??) Is this supposed to be about one user reviewing another user? I don't understand.
It seems to me that you should actually be persisting two IDs, and neither user's email address/name, in this table. So instead, it could be named:
author_id -- The user who wrote the review
owner_id -- The user who owns the review
And in the view, instead of doing this:
<%= form.select :user_reviewed, User.where("email <> ?", current_user.email).pluck(:email, :name), :prompt => "Select one", class: "form-control" %>
You can do something like this:
<%= form.select :owner_id, User.where("id <> ?", current_user.id).pluck(:id, :name), :prompt => "Select one", class: "form-control" %>
Then in the User model, you can draw a clear distinction between:
user.reviews_written # joins on the author_id
user.reviews_owned # joins on the owner_id
And in the controller, you should make use of current_user for what it actually means:
#review = current_user.reviews_written.new(review_params)
Related
I am following ruby-on-rails instruction guide to creating a simple blog web application: https://guides.rubyonrails.org/getting_started.html#generating-a-controller
All my project files are pretty much the same as the ones in the guide.
app/views/articles/show.html.erb
<p>
<strong>Title:</strong>
<%= #article.title %>
</p>
<p>
<strong>Text:</strong>
<%= #article.text %>
</p>
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
<h2>Comments (<%= #article.comments.count %>)</h2>
<%= render 'comment_section' %>
<%#= render #article.comments %>
<%= link_to 'Edit', edit_article_path(#article) %> |
<%= link_to 'Delete', article_path(#article),
method: :delete,
data: {confirm: 'Are you sure?'} %> |
<%= link_to 'Back', articles_path %>
app/views/comments/_form.html.erb
<%= form_with(model: [#article, #article.comments.build], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
app/views/articles/_comment_section.html.erb
<% if #article.comments.count > 0 %>
<%= render #article.comments %>
<% else %>
<p>There are no comments yet!</p>
<% end %>
app/views/comments/_comment.html.erb
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<p>
<%= link_to 'Delete comment', [comment.article, comment],
method: :delete,
data: {confirm: 'Are you sure you want to delete this comment?'}
%>
A simple article with no comments works as expected:
However, when showing an article with some actual comments, an extra empty comment gets displayed at the end:
When I try to delete that comment I get the following error (11 in the path is the article_id):
Deleting other comments works fine.
Rest of the files that I think might be relevant:
app/config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'
resources :articles do
resources :comments
end
root 'welcome#index'
end
app/models/article.rb
class Article < ApplicationRecord
has_many :comments, dependent: :destroy
validates :title, presence: true, length: {minimum: 5}
end
app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :article
end
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
#articles = Article.all
end
def show
#article = Article.find(params[:id])
end
def new
#article = Article.new
end
def edit
#article = Article.find(params[:id])
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comment_params)
redirect_to article_path(#article)
end
def destroy
#article = Article.find(params[:article_id])
#comment = #article.comments.find(params[:id])
#comment.destroy
redirect_to article_path(#article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
I'm using:
ruby 2.6.5p114
Rails 6.0.0
sqlite3 3.8.7.2
RubyMine 2019.2.3
I'm developing on Windows
The reason why this is happening is this line:
<%= form_with(model: [#article, #article.comments.build], local: true) do |form| %>
The part that says #article.comments.build is building an empty comment on the article. If there are no comments on the article and you were to print out #article.comments.count it would be zero. It does this because #article.comments.count runs a query, and since the blank comment isn't saved yet, it doesn't count it against the comments count.
As a side note, #article.comments.size would return 1, since in this case it returns the size of the relation with the blank comment. This is why you don't get a blank comment when the article has no comments.
However if you were to already have a comment and print out #article.comments.count, it would be 1 because now you have a saved comment in the database. This renders your comments out on the page now. The thing is that there is a blank comment inside of the #article.comments return value. This gets printed out to the screen, and since it doesn't have an id, the route for delete gets rendered like this /article/11/comments without a comment id. This route does not exist, so you get an error.
One possible way to fix this would be to change this line in your comment_section partial from this:
<%= render #article.comments %>
to this:
<%= render #article.comments.select { |comment| comment.persisted? %>
UPDATE:
I think that arieljuod's solution is even cleaner, to change this:
<%= form_with(model: [#article, #article.comments.build], local: true) do |form| %>
To this:
<%= form_with(model: [#article, Comment.new], local: true) do |form| %>
in your views/comments/_comment.html.erb
change
<%= link_to 'Delete comment', [comment.article, comment],
method: :delete,
data: {confirm: 'Are you sure you want to delete this comment?'} %>
to
<%= link_to 'Delete comment', comment_path(comment),
method: :delete,
data: {confirm: 'Are you sure you want to delete this comment?'} %>
I have an app where Question model has_many relationship with Option. I also have a button to add options while creating a question. Every question has only one correct answer. So when I create a question and click on Add Option button, new option is created but the new radio button associated with it has different name. In fact the name of radio button is of the form question[options_attributes][i][is_answer] where i is id. As far as I know radio buttons should have the same name to work as a collection or group. So how can I make it work as a group even if I create any number of options for a single question?
html.erb
<%= form_for #question do |form| %>
<div class="field">
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<%= form.fields_for :options, question.options.each do |a| %>
<div class="field">
<%= a.label :options %>
<%= a.text_area :body %>
<%= a.radio_button :is_answer, "options" %>
<%= a.check_box :_destroy %>
<%= a.label :_destroy, 'delete' %>
</div>
<% end %>
<%= form.submit 'Add option', :name => "add_option" %>
<%= form.submit 'Delete options', :name => "remove_option" %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
controller.rb
class QuestionsController < ApplicationController
def new
#question = Question.new
#question.options.build
end
def create
#question = Question.new(question_params)
#question.user = current_user
if params[:add_option]
#question.options.build
else
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' and return }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
render :action => 'new'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def question_params
params.require(:question).permit(:body, options_attributes: [:id, :body, :question_id, :created_at, :updated_at, :is_answer])
end
end
There are two options:
Using JavaScript on the client-side to uncheck the radio buttons.
Using radio buttons with the same name. It this case you will have to change the way you pass the :is_answer parameter and manually assign the value in options_attributes.
Method 1 details:
See this question radio different names - only check one
Method 2 details:
Instead of passing :is_answer parameter for each option you can pass a single parameter for the question having chosen answer id as the value. Lets name it "answer_id". We want this parameter to be in the params[question]
hash in the controller, so the whole name will be "question[answer_id]". Although radio buttons are generated for each option, only the chosen one will be sent to the server as they all have the same name.
<%= form.fields_for :options, question.options.each do |a| %>
<div class="field">
<%= a.label :options %>
<%= a.text_area :body %>
<%= radio_button_tag "question[answer_id]", a.object.id, a.object.is_answer? %>
<%= a.check_box :_destroy %>
<%= a.label :_destroy, 'delete' %>
</div>
<% end %>
https://apidock.com/rails/v4.2.7/ActionView/Helpers/FormTagHelper/radio_button_tag
In the controller you will have to manually assign the option's is_answer parameter based on the answer_id value.
def question_params
result = params.require(:question).permit(:body, :answer_id, options_attributes: [:id, :body, :question_id])
answer_id = result.delete(:answer_id)
result[:options_attributes].values.each do |option_attrs|
option_attrs[:is_answer] = option_attrs[:question_id] == answer_id
end
result
end
If you need further details please let me know. I will update the answer to give more information.
I am new to Rails. I am developing a web application where a user inserts inventory of shoes. So the user enters style code, size, price, and quantity. I want quantity to define how many entries there are in the database. So, if the quantity is three, three separate rows would be created for each shoe. Currently each form submission creates one row in the database.
My create in my shoe_controller:
def create
#shoe = Shoe.new(shoe_params)
respond_to do |format|
if #shoe.save
format.html { redirect_to #shoe, notice: 'Shoe was successfully created.' }
format.json { render :show, status: :created, location: #shoe }
else
format.html { render :new }
format.json { render json: #shoe.errors, status: :unprocessable_entity }
end
end
end
My _form.html.erb
<%= form_with(model: shoe, local: true) do |form| %>
<% if shoe.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(shoe.errors.count, "error") %> prohibited this shoe from being saved:</h2>
<ul>
<% shoe.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :sku %>
<%= form.text_field :sku %>
</div>
<div class="field">
<%= form.label :size %>
<%= form.text_field :size %>
</div>
<div class="field">
<%= form.label :quantity %>
<%= form.number_field :quantity %>
</div>
<div class="field">
<%= form.label :price %>
<%= form.text_field :price %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
What changes would I need to make to achieve the result I am looking for?
Thanks!
#rohit is correct in that using the Shoe.create method will get you what you want. But to implement this, you can change your controller to the following. I'm sure there are much cleaner ways of doing this, but it should get you what you're looking for. Also, I would suggest validating the quantity in shoe_params is a positive integer.
def create
#show = Show.new(shoe_params)
# This will create an array of shoes from your params
new_shoes = (1..shoe_params[:quantity]).collect { Shoe.new(shoe_params) }
# Save each record and put the result in an array
success = new_shoes.map(&:save)
if success.all?
# all records saved
else
# maybe #shoe.valid? is false or something else happened
end
end
you can use ActiveRecord::Persistence#create, which can accept an array of hashes as a parameter.
Suppose quantity is three, so build the 3 new hash which will contain your records.
shoes_params = [
{
code: 1,
size: 8,
price: 300,
quantity: 1
},
{
code: 1,
size: 8,
price: 300,
quantity: 1
}, ...
]
Shoe.create(shoes_params)
So i am getting trouble in saving form data,.Any help will b appreciable
form is submitted without getting any error, but in database, nothing is stored
i am new on rails
users_controller
class UsersController < ApplicationController
def new
#user= User.new
end
def create
#user = User.new(params[:User])
if #user.save
flash[:notice]= "you signed up successfully"
flash[:color]= "valid"
else
flash[:notice]= "failed"
flash[:color]="invalid"
end
render "new"
end
end
new.html.erb
<% page_title="Signup" %>
<div class="Sign_Form">
<h1>Sign up</h1>
<%= form_for(:user, :url => {:controller => 'users', :action => 'create'}) do |f| %>
<p> Username:</br> <%= f.text_field :username %> </p>
<p> Email:</br> <%= f.text_field :email %> </p>
<p> Password:</br> <%= f.password_field :password %></p>
<p> Password Confirmation:</br> <%= f.password_field :password_confirmation %> </p>
<%= f.submit :Signup %>
<% end %>
<% if #user.errors.any? %>
<ul class="Signup_Errors">
<% for message_error in #user.errors.full_messages %>
<li>* <%= message_error %></li>
<% end %>
</ul>
<% end %>
</div>
in app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :password
EMAIL_REGEX = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i
validates :username, :presence => true, :uniqueness => true, :length => { :in => 3..20 }
validates :email, :presence => true, :uniqueness => true #:format => EMAIL_REGEX
validates :password, :presence =>true #:confirmation => true #password_confirmation attr
validates_length_of :password, :in => 6..20, :on => :create
end
In users_controller, create method, you are using
#user = User.new(params[:User])
replace it with following code, hope it will work fine.
#user = User.new(params[:user])
And,also use strong params if you are using rails 4. Like follwoing.
def create
#user = User.new(user_params)
if #user.save
flash[:notice]= "you signed up successfully"
flash[:color]= "valid"
else
flash[:notice]= "failed"
flash[:color]="invalid"
end
render "new"
end
private
def user_params
params.require(:user).permit(:username, :account, :email, :password, :password_confirmation)
end
If, it still not works, then, please display your log.
If you are using Rails 4 you need to use strong parameters to whitelist the parameters you want to assign to your models.
This became non-optional in Rails 4 to prevent mass-assignment vulnerabilities where a malicious user can assign any property to a model after Egor Homakovs much publicised Github attack.
Also note that Ruby is case sensitive. This applies to hash keys as well:
irb(main):003:0> hash = { a: 1 }
=> {:a=>1}
irb(main):004:0> hash[:A]
=> nil
Which is why why you do User.new(params[:User]) you are actually doing User.new(nil)
This is a corrected version of your controller
class UsersController < ApplicationController
def new
#user= User.new
end
def create
#user = User.new(params[:User])
if #user.save
flash[:notice]= "you signed up successfully"
flash[:color]= "valid"
# You should redirect instead of rendering the form again
redirect_to #user # or redirect_to root_path
else
flash[:notice]= "failed"
flash[:color]="invalid"
render "new" # Needs to be inside the "else" statement
# Otherwise you will get a double render error
end
end
def user_params
params.require(:user)
.allow(:username, :email, :password, :password_confirmation)
end
end
Added:
You can also simplify your form_for to
<%= form_for(:user) do |f| %>
Rails will by convention route the form to UserController#create.
Also you should use <label> tags for accessibility, as they help people who use assistive technology such as screen readers to find the correct inputs.
By using the built in label helper rails will set up the for attribute and you can translate the label texts with Rails built in I18n functionality.
<% page_title="Signup" %>
<div class="Sign_Form">
<h1>Sign up</h1>
<%= form_for(:user) do |f| %>
<div class="row">
<%= f.label :username %>:</br>
<%= f.text_field :username %>
</div>
<div class="row">
<%= f.label :email %>:</br>
<%= f.text_field :email %>
</div>
<div class="row">
<%= f.label :password %>:</br>
<%= f.password_field :password %>
</div>
<%= f.submit :Signup %>
<% end %>
<% if #user.errors.any? %>
<ul class="Signup_Errors">
<%# for loops are almost never used in ruby. %>
<%# .each is the idiomatically correct way %>
<% #user.errors.full_messages.each do |message_error| %>
<li>* <%= message_error %></li>
<% end %>
</ul>
<% end %>
</div>
I am using rails 4 and have a subject and comment models. Subject is a one to many relationship with comments. I want a simple page that can add comments to many subjects on the same page. So in my form I know how to submit a comment to create but I dont know how to find the right subject in my controller to add it to. Any advice?
class CommentsController < ApplicationController
def create
comment = Comment.create(comment_params)
if comment.save
# The line below is incorrect, I dont know what to do
Subject.find(params[:subject_id]).comments << comment
redirect_to(:controller => 'static_pages', action: 'home')
end
end
def new
end
private
def comment_params
params.require(:comment).permit(:text, :user_name)
end
end
StaticPages#home Find me in
app/views/static_pages/home.html.erb
<% #subjects.each do |subject| %>
<div class="subjects <%= cycle('odd', 'even') %>">
<h1><%= subject.name %></h1>
<h3><%= subject.description %></h3>
<% subject.comments.each do |comment|%>
<div class="comment">
<h4><%= comment.user_name%></h4>
<%= comment.text %>
</div>
<% end %>
<%= form_for(#comment) do |f| %>
<%= f.label :user_name %>
<%= f.text_field :user_name %>
<%= f.label :text %>
<%= f.text_field :text %>
<%= f.submit('Create comment', subject_id: subject.id) %>
<% end %>
</div>
<% end %>
The simplest way would be to populate the subject_id attribute of your #comment form, like this:
<%= form_for(#comment) do |f| %>
<%= f.label :user_name %>
<%= f.text_field :user_name %>
<%= f.label :text %>
<%= f.text_field :text %>
<%= f.hidden_field :subject_id, value: subject.id %>
<%= f.submit('Create comment', subject_id: subject.id) %>
<% end %>
This will populate the subject_id attribute of your new Comment object, which will essentially associate it through Rails' backend:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def create
#comment = Comment.new comment_params
#comment.save
end
private
def comment_params
params.require(:comment).permit(:subject_id, :text, :user_name)
end
end
--
foreign_keys
This works because of the Rails / relational database foreign_keys structure
Every time you associate two objects with Rails, or another relational database system, you basically have a database column which links the two. This is called a foreign_key, and in your case, every Comment will have the subject_id foreign_key column, associating it with the relevant subject
So you may have many different forms using the same #comment variable - the trick is to populate the foreign_key for each one