Rails: Unable to save nested attributes - ruby-on-rails

I have two models: User and WritingSample. A user can have many writing samples. I have a page with a form with extra JS for adding additional writing sample fields -- however, I've disabled this for the time being and I'm trying to get it working with just the one.
What I want to happen: the user types the url of a writing sample into the form and this is stored as a WritingSample row.
The form seems to be working. But I'm clearly not doing something right.
When I submit the form, I can see that safe_params contains:
"writing_samples_attributes"=><ActionController::Parameters {"0"=><ActionController::Parameters {"url"=>"foo"} permitted: true>}
But it looks like User wants an id:
> User
User(id: integer, name: string, writing_sample_id: integer)
User.new(safe_params) returns:
<User id: nil, name: "joe", created_at: nil, updated_at: nil, writing_sample_id: nil>
The new user isn't created, and I get the error:
user.errors.first
[:"writing_samples.user", "must exist"]
What steps do I need to take to store the submitted data in the :url field?
WritingSample:
class WritingSample < ApplicationRecord
belongs_to :user
end
User:
class User < ApplicationRecord
has_many :writing_samples
accepts_nested_attributes_for :writing_samples
end
Schema:
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "writing_sample_id"
t.index ["writing_sample_id"], name: "index_users_on_writing_sample_id"
end
create_table "writing_samples", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "url"
t.index ["user_id"], name: "index_writing_samples_on_user_id"
end
My controller:
def create
user = User.new(safe_params)
byebug
if user.save
redirect_to '/thanks'
else
byebug
redirect_to new_creator_path
end
end
private
def safe_params
params.require(:user).permit(:name, :writing_samples_attributes => [:url])
end
The form:
<%= form_for(#user, url: creators_path) do |f| %>
<%= f.label :name %>
<%= f.label 'Writing samples' %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.fields_for :writing_samples do |sample_f| %>
<%= sample_f.text_field :url %>
<% end %>
<% end %>

Related

I don't understand the new and create associations in rails

What I want to achieve
I have created an association between movies and schedules, but I don't know how to instantiate the association, even after reading the Rails Guide.
Code
db/schema
# movies
create_table "movies", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name"
t.integer "year"
t.string "description"
t.string "image_url"
t.integer "is_showing"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
#schedules
create_table "schedules", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "movie_id", null: false
t.time "start_time", null: false
t.time "end_time", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["movie_id"], name: "index_schedules_on_movie_id"
end
model
movie.rb
class Movie < ApplicationRecord
has_many :schedules
end
schedule.rb
class Schedule < ApplicationRecord
belongs_to :movie
end
controller
schedules
class SchedulesController < ApplicationController
def index
#movies = Movie.joins(:schedules).select("movies.*", "schedules.*")
end
###############################################################################
# I don't get it.
def new
#schedule = #movie.build_schedules()
end
def create
#schedule = Schedule.new(schedule_params)
#schedule.save
redirect_to schedules_path
end
###############################################################################
private
def schedule_params
params.require(:schedule).permit(:start_time, :end_time)
end
end
new.html.erb
<!DOCTYPE html>
<html lang="en">
<head>
<%= render 'shared/head' %>
<title>schedule/new</title>
</head>
<body>
<%= form_with model: #schedule, url: movie_schedules_path do |form| %>
<div class="field">
<%= form.label :start_time %>
<%= form.date_field :start_time %>
</div>
<div class="field">
<%= form.label :end_time %>
<%= form.date_field :end_time %>
<%= form.submit %>
</div>
<% end %>
</body>
</html>
What I've tried.
・Check the Rails Guide.
I was using build instead of new, but it took an argument. I didn't know what I needed for the argument part here.
# Rails Guide
#book.author = #author
#author = #book.build_author(author_number: 123,
author_name: "John Doe")
You want to do #movie.schedules.build to instantiate a new record for the #movie.schedules association because it's a has_many.
You can also do #movie.schedules.create(schedule_params) to create the Schedule associated to the Movie instance in one step.
record.build_xxx only works for has_many/belongs_to associations (you could do some_schedule.build_movie for example).

I keep getting this error <undefined method `category_id' for #<ActionController::Parameters>>

Im currently trying to create a search form and everytime I try to send a request rails always report me the NoMethodError above. Below is my code.
controller
#search_cat = params['search_cat']
if #search_cat.present?
category_id = #search_cat['category_id']
#tasks = Task.where(user_id: current_user.id, category_id: category_id.to_i)
end
view
<%= simple_form_for :search_cat, url: tasks_index_path, method: "GET", html: { class: 'form-inline' } do |f| %>
<%= f.input :category_id, collection: current_user.categories,as: :select %>
<%= f.submit "Search", class: "btn btn-primary" %>
<% end %>
def tasks_params
params.require(:task).permit(:deadline_at,:title, :tags, :note, :is_done, :category_id, :user_id, {tag_ids: []})
end
edit:
class Task < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, optional: true
has_many :tag_associations, dependent: :destroy
has_many :tags, through: :tag_associations
end
schema
create_table "tasks", force: :cascade do |t|
t.datetime "deadline_at"
t.string "title"
t.string "note"
t.boolean "is_done"
t.integer "user_id"
t.integer "category_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["category_id"], name: "index_tasks_on_category_id"
t.index ["user_id"], name: "index_tasks_on_user_id"
I basiclly just trying send the input from the search form to query controller.
enter image description here
My guess would be that you don't have a category reference set in your task table. I would double check your schema to make sure you have all the columns within your task table match your params that you are permitting in your controller. Also make sure you have your relationships set within your models between your task model and category model.

Associating a reply with a post

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"}

Can't update User belongs_to association in Rails

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

Rails searching a has_many relationship

Heyo. Been trying to figure this out but I've been stuck too long and it's just getting painful!
I'm trying to do an Advanced Search form allowing you to search for Users based off settings that are in other models. e.g. Search for a User named Jim, who does Running, and has weight loss as his goal.
I have three models:
User (using Devise)
Sport (many-to-many with user)
Goals (user has_many goals, goal belongs_to user)
So far I have managed to get it working so I can search for things in the User model (such as name) and also for Users Sports through a select box. What I haven't been able to get working is searching for the Users goals and I don't get why.
What I get is "Nobody seems to have these preferences" when searching ONLY for Goals and no other fields.
I have tried using the same code as my Sports but that didn't work (guessing because of the different relationships?)
# searches/show.html.erb
<% if #search.search_users.empty? %>
<p>Nobody seems to have these preferences</p>
<% else %>
<% #search.search_users.each do |u| %>
<tr>
<td><%= u.name %></td>
<% u.sports.each do |s| %>
<td><%= s.name %></td>
<% end %>
<% u.goals.each do |g| %>
<td><%= g.name %></td>
<% end %>
</tr>
<% end %>
I've done associations in the console and when I type for example u.goals I get this (and the opposite when I query what users are associated with a goal):
irb(main):015:0> u.goals
=> #<ActiveRecord::Associations::CollectionProxy [#<Goal id: 1, name: "Weight Loss", user_id: 1>, #<Goal id: 3, name: "Strength", user_id: 1>]>
Here's my current code:
# user.rb
class User < ApplicationRecord
has_and_belongs_to_many :sports
has_many :goals, :foreign_key => :goal_id
end
# sport.rb
class Sport < ApplicationRecord
has_and_belongs_to_many :users
end
# goal.rb
class Goal < ApplicationRecord
belongs_to :user, :foreign_key => :goal_id
end
And my searches stuff:
# search.rb
def search_users
users = User.all
users = users.where("users.name ILIKE ?", "%#{keywords}%") if keywords.present?
users = users.joins(:sports).where("sports.name ILIKE ?", "%#{name}%") if name.present?
users = users.where(goal_id: goal_id) if goal_id.present?
return users
end
# searches/new.html.erb
<%= form_for #search do |s| %>
<div class="form-group">
<%= s.label :keywords %>
<%= s.text_field :keywords %>
</div>
<div class="form-group">
<%= s.label :exercise %>
<%= s.select :name, options_for_select(#s_names), include_blank: true %>
</div>
<div class="form-group">
<%= s.label :goals %>
<%= s.collection_select :goal_id, Goal.order(:name), :id, :name, include_blank: true %>
</div>
<%= s.submit "Search", class: "btn btn-primary" %>
<% end %>
# searches_controller.rb
class SearchesController < ApplicationController
def new
#search = Search.new
#s_names = Sport.uniq.pluck(:name)
#users = User.uniq.pluck(:name)
end
def create
#search = Search.create(search_params)
redirect_to #search
end
def show
#search = Search.find(params[:id])
end
private
def search_params
params.require(:search).permit(:keywords, :name, :goal_id)
end
end
and then my schema for reference:
create_table "goals", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.index ["user_id"], name: "index_goals_on_user_id", using: :btree
end
create_table "searches", force: :cascade do |t|
t.string "keywords"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "goal_id"
t.index ["goal_id"], name: "index_searches_on_goal_id", using: :btree
end
create_table "sports", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "sports_users", id: false, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "sport_id", null: false
t.index ["user_id", "sport_id"], name: "index_sports_users_on_user_id_and_sport_id", using: :btree
end
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.string "name"
t.integer "movement_id"
t.integer "goal_id"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
t.index ["goal_id"], name: "index_users_on_goal_id", using: :btree
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
end
add_foreign_key "goals", "users"
end
Extremely sorry for the huge amount of messy code but I'm just tripping over myself at this point and getting confused.
Thank you greatly in advance.
I would change
users = users.where(goal_id: goal_id) if goal_id.present?
To
users = users.joins(:goals).where(goals: {id: goal_id})
For advanced searching I've recently used approach presented in this article: http://www.justinweiss.com/articles/search-and-filter-rails-models-without-bloating-your-controller/ and I think it's worth reading if you think about expanding search options.
EDIT: full response in comments below
:foreign_key => :goal_id needed to be removed

Resources