I'm trying to build a very basic forum-like app, where Users can create Topics and reply to existing Topics.
The Topic creation works fine and I'm able to display Reply form, however, the Reply create action is not working properly. I don't have any errors, it just redirects_to topics_path.
This is following a tutorial, so the code is not mine. Is anyone able to spot the obvious cause for this? Any help much appreciated!
replies_controller.rb
def create
#topic = Topic.find(params[:topic_id])
#reply = #topic.replies.create(params[:reply].permit(:reply))
#reply.user_id = current_user.id if current_user
#reply.save
if #reply.save
redirect_to topic_path(#topic)
else
flash[:notice] = "Error."
redirect_to topics_path
end
end
reply.rb
class Reply < ApplicationRecord
belongs_to :post
belongs_to :user
end
replies/_form.html.erb
<%= form_for [#topic, #topic.replies.create] do |f| %>
<%= f.label :reply %>
<%= f.text_area :reply, class: "textarea", rows: "10" %>
<%= f.submit class: "button is-primary" %>
<% end %>
topic.rb
class Topic < ApplicationRecord
belongs_to :user
has_many :replies
end
schema.rb
create_table "topics", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "replies", force: :cascade do |t|
t.text "reply"
t.bigint "topic_id"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["topic_id"], name: "index_replies_on_topic_id"
t.index ["user_id"], name: "index_replies_on_user_id"
end
In file replies/_form.html.erb
you should use build method build instead of create. Replace line:
<%= form_for [#topic, #topic.replies.create] do |f| %>
to
<%= form_for [#topic, #topic.replies.build] do |f| %>
There are some another problems with the code:
#reply = #topic.replies.create(params[:reply].permit(:reply))
In this line you call new + save, without user, which is required.
Change this to:
#reply = #topic.replies.new(params[:reply].permit(:reply))
Then, you call save twice:
#reply.save
if #reply.save
...
First line is unnecessary.
And finally, what is the cause of rollback, in your Reply model you have:
belongs_to :post
But in the schema.rb and in the params you have topic:
Schema.rb:
t.bigint "topic_id"
Params:
"reply"=>{"reply"=>"Test reply"}, "commit"=>"Create Reply", "topic_id"=>"4"}
Related
I am beginner with Ruby on Rails, and I am trying to build a little app that permit people to order a pastrie from a cooker, for a day chosen.
When they select the pastrie using a checkbox, I would like that the selected pastrie was saved as pastrie_id in the table fight but instead, I have an issue :
Validation failed: Pastrie must exist.
UPDATE this is working :
<%= f.association :pastrie, as: :check_boxes, label: 'Pastrie' %>
I still have an issue, the saving params are not good I have :
"fight"=>{"pastrie_id"=>["", "1"]},
I tried a lot of solutions found on stackoverflow but nothing seems to work.
I am using Rails 5.2.3
So, this is my schema :
ActiveRecord::Schema.define(version: 2019_06_06_094318) do
create_table "cookers", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "pastrie"
end
create_table "events", force: :cascade do |t|
t.datetime "date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "fights", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "pastrie_id"
t.integer "event_id"
t.integer "cooker_id"
t.index ["cooker_id"], name: "index_fights_on_cooker_id"
t.index ["event_id"], name: "index_fights_on_event_id"
t.index ["pastrie_id"], name: "index_fights_on_pastrie_id"
end
create_table "pastries", force: :cascade do |t|
t.string "pastrie_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Here is my models :
class Event < ApplicationRecord
has_many :pastries, through: :fights
has_many :cookers, through: :fights
has_many :fights
end
class Fight < ApplicationRecord
belongs_to :pastrie
belongs_to :event
belongs_to :cooker, optional: true
end
class Pastrie < ApplicationRecord
has_many :fights
has_many :events, through: :fights
end
This is my controller. What I understand is : in order to create a Fight, I need an event_id and a pastrie_id (cooker_id is optional). So first, I create a new event (and so I have an event_id), and next, I need to connect a pastrie_id (existing in my seed) to my fight. But this is not working if I am doing that :
class FightsController < ApplicationController
def new
#fight = Fight.new
#event = Event.find(params[:event_id])
#pastries = Pastrie.all
end
def create
#fight = Fight.new(fight_params)
#event = Event.find(params[:event_id])
#fight.event = Event.find(params[:event_id])
#fight.pastrie_id = params[:fight][:pastrie_id]
#fight.save!
redirect_to root_path
end
def show
#events = Event.all
end
def index
#fights = Fight.all
fights_by_event = []
end
private
def fight_params
params.require(:fight).permit(:event_id, :pastrie_id, :cooker_id)
end
end
And my view when I am creating my "fight" :
<div class=margin-bottom>
<h2 class=text-center> Bonjour Linguini, quelles patisseries veux-tu choisir ?</h2>
</div>
<div class="container margin-bottom">
<%= simple_form_for [#event, #fight] do |f| %>
<% #pastries.each do |pastrie| %>
<%= f.label :pastrie_id do %>
<%= f.check_box :pastrie_id, as: :boolean, checked_value: true, unchecked_value: false %> <span><%= pastrie.pastrie_name%></span>
<% end %></br>
<% end %>
<%= f.submit "Valider", class: "btn btn-primary" %>
<% end %>
</div>
And this is my routes if you need this :
Rails.application.routes.draw do
root to: 'pages#home'
resources :events do
resources :fights, only: [:new, :show, :create]
end
resources :fights, only: [:index]
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
In your form change
<%= f.check_box :pastrie_id, as: :boolean, checked_value: true, unchecked_value: false %> <span><%= pastrie.pastrie_name%></span>
To
<%= f.check_box :pastrie_id, as: :boolean, checked_value: pastrie.id, unchecked_value: nil %> <span><%= pastrie.pastrie_name%></span>
In the first version you submit a param of {pastrie_id: true}, which obviously doesn't relate to a Pastry. The second version should submit the ID of the checked box (although if it belongs to only 1 pastry it might make more sense to make these radio buttons)
I am trying to implement an advanced search on all microposts (by keyword and by purpose -dropdown options-) but I can not even write to the database searches. I guess my problems could be: models construction, collection_select set up, or controller (mostly everywhere).
routes.rb has all resources, but :searches is not nested
new.html.erb:
<%= form_for #search do |f| %>
<%= f.label :keywords %><br>
<%= f.text_field :keywords %>
<%= f.label :purpose_id %><
<%= f.collection_select :purpose_id, Micropost.order(:purpose), :id, :purpose_id, include_blank: true %>
<%= f.submit 'Buscar' %>
<% end %>
searches_controller.rb:
class SearchesController < ApplicationController
def new
#search = Search.new
end
def create
#search = Search.new(params[:search])
if #search.save
redirect_to #search
else
redirect_to root_url
end
end def show
#search = Search.find(params[:id])
end
private
def search_params
params.require(:search).permit(:keywords, :purpose_id)
end
schema.db:
create_table "microposts", force: :cascade do |t|
t.text "content"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "picture"
t.string "purpose"
t.index ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at"
t.index ["user_id"], name: "index_microposts_on_user_id"
end
create_table "searches", force: :cascade do |t|
t.string "keywords"
t.integer "purpose_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Error from console:
Processing by SearchesController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"wA3FRz....6Kg==", "search"=>{"keywords"=>"", "purpose_id"=>"18"}, "commit"=>"Buscar"}
Completed 500 Internal Server Error in 3ms (ActiveRecord: 0.0ms)
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/searches_controller.rb:8:in `create'
You're passing the raw params directly into your model's constructor, rather than search_params where you require/permit specific parameters. search_param needs to be invoked to do anything.
The line
#search = Search.new(params[:search])
needs to be
#search = Search.new(search_params)
I have this method in my micropost.rb, the error is souly when I run this method. When I submit my form I do not get the errors from the model but the rendering of the else condition in the controller. Something seems to be wrong in the model but don't see why I get rollback transaction from server?
The method just checks that a user has not entered and sent more than one field.
Micropost.rb
validate :return_media_field_errors
private
def return_media_field_errors
if :img_url? && :video_url?
errors.add(:img_url, "Can only submit one field at a time")
end
end
MicropostController
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home'
else
#feed_items = []
render 'shared/error_messages'
end
end
private
def micropost_params
params.require(:micropost).permit(:content, :picture, :picture_cache, :image_url, :video_url, :gif_url)
end
micropost schema.rb
create_table "microposts", force: :cascade do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "picture"
t.string "image_url"
t.string "video_url"
t.string "gif_url"
t.index ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at"
t.index ["user_id"], name: "index_microposts_on_user_id"
end
View params
<div class="media_field_1">
Image <%= f.text_field :image_url, class: 'form-control' %>
</div>
<div class="media_field_2">
Video <%= f.text_field :video_url, class: 'form-control' %>
</div>
I think the problem is this line:
if :img_url? && :video_url?
here :img_url? is a symbol, not an attribute
What you want is self.image_url? or just image_url, same for video_url
I've got a page where a User (using Devise) sets up multiple preferences through checkboxes and then radio buttons of predefined data. So far I have the user able to update a has_and_belongs_to_many association but I can't get my belongs_to one's working.
At the moment I've got this error with the following parameters shown:
PG::ForeignKeyViolation: ERROR: insert or update on table "users" violates foreign key constraint
{"utf8"=>"✓",
"_method"=>"patch",
"user"=>{"sport_ids"=>["4"], "goal_ids"=>["6"], "moment_id"=>"moment_id", "workout_id"=>"workout_id"},
"commit"=>"Save Changes",
"id"=>"1"}
It seems clear that I'm not passing an id number through but I don't know how to fix it. When I don't get an error nothing happens.
Here are my files
models/user.rb
class User < ApplicationRecord
belongs_to :city
belongs_to :workout
belongs_to :fitness_level
belongs_to :moment
has_and_belongs_to_many :sports
has_and_belongs_to_many :goals
has_and_belongs_to_many :gyms
end
controllers/users_controller.rb
class UsersController < ApplicationController
...
def edit
#user = User.find(params[:id])
#auth = current_user.id
# To make sure you can't edit someone elses profile
if #auth != #user.id
redirect_to #user
end
#sports = Sport.all.order(name: :asc)
#goals = Goal.all.order(name: :asc)
#workouts = Workout.all.order(:name)
#moments = Moment.all
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(sport_ids: [], goal_ids: [])
params.require(:user).permit(:workout_id, :moment_id)
end
end
users/edit.html.erb
<%= simple_form_for #user do |f| %>
# The following two work
<% #sports.each do |sport| %>
<%= check_box_tag "user[sport_ids][]", sport.id, form.object.sports.include?(sport) %>
<%= sport.name %>
<% end %>
<% #goals.each do |goal| %>
<%= check_box_tag "user[goal_ids][]", goal.id, form.object.goal.include?(goal) %>
<%= sport.name %>
<% end %>
# the below doesn't work
<% #moments.each do |moment| %>
<%= radio_button_tag 'user[moment_id]', :moment_id %>
<h4><%= moment.name %></h4>
<% end %> <!-- end moments-->
<% #workouts.each do |workout| %>
<%= radio_button_tag 'user[workout_id]', :workout_id %>
<% end %> <!-- end workouts-->
<% end %> <! -- end form -->
I have some important styling with the forms using tags so that will need to stay.
EDIT: Adding Users table in schema
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.jsonb "settings", default: {}, null: false
t.string "first_name"
t.string "last_name"
t.date "date_of_birth"
t.integer "city_id"
t.text "bio"
t.integer "workout_id"
t.integer "fitness_level_id"
t.integer "moment_id"
t.index ["city_id"], name: "index_users_on_city_id", using: :btree
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
t.index ["fitness_level_id"], name: "index_users_on_fitness_level_id", using: :btree
t.index ["moment_id"], name: "index_users_on_moment_id", using: :btree
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
t.index ["settings"], name: "index_users_on_settings", using: :gin
t.index ["workout_id"], name: "index_users_on_workout_id", using: :btree
end
Im pretty sure the problem in that line:
<%= radio_button_tag 'user[moment_id]', :moment_id %>
You dont pass moment_id and workout_id from the view to controller`s update action.
Try to change it to:
<% #moments.each do |moment| %>
<%= radio_button_tag 'user[moment_id]', moment.id %>
<h4><%= moment.name %></h4>
<% end %> <!-- end moments-->
The same is for workout:
<% #workouts.each do |workout| %>
<%= radio_button_tag 'user[workout_id]', workout.id %>
<% end %> <!-- end workouts-->
Also why dont you pass permitted params in one line? Like this:
def user_params
params.require(:user).permit(:moment_id, :workout_id, sport_ids: [], goal_ids: [])
end
I have three models: User, Post and, Reply. An user has many posts and comments. A post had many replies and belongs to an user, and a reply belongs to a post and an user.
routes.rb:
resources :posts do
resources :replies
end
schema.rb:
create_table "posts", :force => true do |t|
t.text "content", :limit => 255
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "title"
end
create_table "replies", :force => true do |t|
t.text "content"
t.integer "post_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
This is how I'm creating comments:
comments_controller.rb:
def create
#post = Post.find(params[:post_id])
#reply = #post.replies.build(params[:reply])
if #reply.save!
flash[:success] = "reply created!"
redirect_to post_path(#post)
else
redirect_to post_path(#post)
end
end
replies/_form.html.erb:
<%= form_for([#post, #post.replies.build]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Enter reply content" %>
</div>
<%= f.submit "Reply", class: "btn btn-large btn-primary" %>
<% end %>
After submitting the form I get this error:
Validation failed: User can't be blank
I assume its because the reply's attribute user_id is empty:
reply.rb
validates :user_id, presence: true
I'm not sure how to fill that attribute. I can't just put it in Reply attr_accesible, because that would compromise the security of the app (as far as I know).
Any suggestions to solve this?
attr_acessible only affects things when you're updating/creating a record from a hash of attributes. You can always set an attribute by calling the accessor directly, so after you've built the reply,
#reply.user = current_user
Should do the trick (assuming you're using something like devise or authlogic that defined current_user for you. You could also assign to #reply.user_id directly.