Can't update User belongs_to association in Rails - ruby-on-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

Related

Retrieving a piece of data from a different Active Record Model/table

Any advice would be most appreciated.
Is it possible to retrieve the email address used by a user, posting a comment, in a list#show/show.html.erb view? Without adding another column "email" to my Comments model?
The best I can do is retrieve the user_id, which is not that helpful.
<% #list.comments.each do |comment| %>
<p><%= comment.body%></p>
<p><%= comment.user_id %>
<% end %>
Comment.rb
class Comment < ApplicationRecord
belongs_to :list, optional: true
belongs_to :user
Comments Table
create_table "comments", force: :cascade do |t|
t.text "body"
t.bigint "list_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "user_id"
t.index ["list_id"], name: "index_comments_on_list_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
List controller#show
def show
#list = List.find(params[:id])
#current_user = current_user.id
end
Use Module#delegate from ActiveSupport:
class Comment < ApplicationRecord
belongs_to :list, optional: true
belongs_to :user
delegate :email, to: :user
end
<% #list.comments.each do |comment| %>
<p><%= comment.body%></p>
<p><%= comment.user_id %>
<p><%= comment.email %>
<% end %>
And make sure you use .includes or .eager_load in the controller to avoid a N+1 query:
def show
#list = List.includes(comments: :user).find(params[:id])
#current_user = current_user.id
end

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

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

Getting no method error in Customers#index while trying to access attribute of Order model

How to access customer's name from Customer table onto my Order's action view index. And vice versa.
I am getting this error:
NoMethodError in Customers#index.
Undefined method `order' for #Customer:0x24f4...
class Customer < ActiveRecord::Base
has_many :orders, foreign_key: "customer_id"
end
class Order < ActiveRecord::Base
belongs_to :customer
end
In my migration:
create_table "customers", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "orders", force: :cascade do |t|
t.integer "customer_id"
t.datetime "orderdate"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "orders", ["customer_id"], name: "index_orders_on_customer_id", using: :btree
end
In customer's index.html.erb
<% #customers.each do |customer| %>
<%= customer.name %>
<%= customer.order.orderdate %>
<% end %>
In orders's index.html.erb
<% #orders.each do |order| %>
<%= order.orderdate %>
<%= order.customer.name %>
<% end %>
you have has many relation with customer and order so
In customer's index.html.erb
<% #customers.each do |customer| %>
<%= customer.name %>
<!-- this will display customers all order and fetch first and then show that order's orderdate -->
<%= customer.orders.first.orderdate %>
<% end %>
Try a different approach to setting the relationships between your models in the migration:
t.belongs_to :customer, index: true
See http://guides.rubyonrails.org/association_basics.html#the-belongs-to-association

User_id is not fetched in join table

I have a the following form:
<h2>Add collaborators to the wiki <strong><%= #wiki.title %></strong></h2>
<%= form_for ([#wiki, #collaboration]) do |f| %>
<% #users.each do |user| %>
<p><%= check_box_tag 'user_ids[]', user.id %>
<%= label_tag 'user_ids[]', user.email %>
<% end %>
<p> <%= f.submit %> <p>
<% end %>
It should do the following, provide the possible the check users => an then all this users should be able to edit this particular form (#wiki)
I therefor created a join table which takes a user_id and wiki_id. If i try to
to save the collaborators in through the form it does not seem to work however.
I get this in my rails c
#<Collaboration id: 1, user_id: nil, wiki_id: 1, created_at: "2015-02-20 10:40:49", updated_at: "2015-02-20 10:40:49">,
So it does not seem to fetch the user.
My controller is set up like this
class CollaborationsController < ApplicationController
def new
#wiki = Wiki.find(params[:wiki_id])
#collaboration = #wiki.collaborations.new
#users = User.all
end
def create
#wiki = Wiki.find(params[:wiki_id])
#selected users
#collaboration = #wiki.collaborations.build(user_id: params[:user_id])
if #collaboration.save
redirect_to wikis_path, notice: "Wiki shared."
else
flash[:error] = "Error creating wiki. Try again."
render :new
end
end
end
And my schema file looks like this:
create_table "collaborations", force: :cascade do |t|
t.integer "user_id"
t.integer "wiki_id"
t.datetime "created_at"
t.datetime "updated_at"
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.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "role"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
create_table "wikis", force: :cascade do |t|
t.string "title"
t.text "body"
t.boolean "private"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "wikis", ["user_id"], name: "index_wikis_on_user_id"
create_table "wikis_and_collaborators", force: :cascade do |t|
t.integer "user_id"
t.integer "wiki_id"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Any thoughts on what goes wrong here?
Let's say we have params[:user_ids] = [123, 456, 789]
You can say #wiki.user_ids = [123, 456, 789]; #wiki.save and that will make the join records automatically. So, this is actually an update on the wiki object, and your form should be editing the Wiki object too. I would do it like so:
<h2>Add collaborators to the wiki <strong><%= #wiki.title %></strong></h2>
<%= form_for (#wiki) do |f| %>
<% #users.each do |user| %>
<p><%= check_box_tag 'wiki[user_ids][]', user.id, #wiki.user_ids.include?(user.id) %>
<%= label_tag 'wiki[user_ids][]', user.email %>
<% end %>
<p> <%= f.submit %> <p>
<% end %>
This will submit to the WikiController#update action, or the WikiController#create action, depending on whether #wiki is a new record or not.
params will be params = {:id => 6, :wiki => {:user_ids => [123, 456, 789]}}, where 6 is an example wiki id.
Following convention, you wouldn't be accessing the CollaborationsController at all, you'd be accessing the WikisController, since it's a Wiki that's being updated. The WikisController#update action would be totally standard:
def update
#wiki = Wiki.find_by_id(params[:id])
#wiki.update_attributes(params[:wiki])
redirect_to wiki_path(#wiki) #or whatever
end

Resources