Rails association doesn't work - ruby-on-rails

I am having a weird issue that I can't figure out.
It is very basic rails programming : I want to create an association between a user model and a goal model.
goal.rb
class Goal < ActiveRecord::Base
belongs_to :user
end
user.rb
class User < ActiveRecord::Base
has_many :goals, dependent: :destroy
has_many :records
has_many :orders
end
When I am making the association from the console, it is working well, lets say :
$ goal = Goal.first
$ goal.user_id = 1
$ goal.save
$ goal.inspect
#<Goal id: 1, description: "loremipsum", created_at: "2016-11-26 12:39:34", updated_at: "2016-11-26 12:43:41", name: "ipsumlorem", user_id: 1>
But then, when I am creating a goal from my views, the association is not made, and the user_id of the goal user_id remain : nil.
Any ideas ?
EDIT AS REQUIRED :
_form.html.erb
<%= form_for(#goal) do |f| %>
<%= f.text_field :name, class: "form-control" %>
<%= f.text_area :description, class: "form-control" %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
goal_controller.rb
def new
#users = User.all
#goal = current_user.goals.build
end
def edit
end
def create
#goal = Goal.new(goal_params)
#goal.save
redirect_to root_path, notice: "Objectif sauvegardé"
end

def create
# Here!!!!
#goal = current_user.goals.new(goal_params)
#goal.save
redirect_to root_path, notice: "Objectif sauvegardé"
end

Related

Adding a student to a team when creating a team in a has_and_belongs_to_many association

I have two models (teams and students) and when creating a team I want to be able to add a student to the team using their email. I can do this in the rails console by doing team.students << student but I am unsure how to translate that functionality in the controller and view.
Team controller:
def new
#team = Team.new
end
def add_student
#team = Team.find(params[:id])
#team.students << Student.find(params[:student_email])
end
def create
#team = Team.new(team_params)
if #team.save
redirect_to teams_path
else
render 'new'
end
end
private
def team_params
params.require(:team).permit(:name, student_attributes:[])
end
def current_team
team = Team.find(params[:id])
end
end
Team view:
<%= form_with(model: #team, local: true) do |f| %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= fields_for :student do |s|%>
<%= s.label :email%>
<%= s.text_field :email, class: 'form-control' %>
<% end %>
<%= f.submit "Create Team", class: "btn btn-primary" %>
<% end %>
Thank you for your help
You can do a lot better then just using HABTM:
class Team < ApplicationRecord
has_many :memberships
has_many :students, through: :memberships
end
class Student < ApplicationRecord
has_many :memberships
has_many :teams, through: :memberships
end
# rails g model team student:belongs team:belongs_to
class Membership < ApplicationRecord
belongs_to :student
belongs_to :team
validates_uniqueness_of :student_id, scope: :team_id
end
This also creates a many to many assocation but it gives you an actual model so you can access additional columns on the table (like for example if you want to add roles or keep track of who added the student to the team) and its actually a real entity in your buisness logic instead of just a peice of plumbing.
HABTM is actually quite useless.
To add/remove members from a team you create and destroy memberships.
resources :teams do
resources :memberships,
only: [:new, :create, :destroy]
shallow: true
end
class MembershipsController < ApplicationController
before_action :set_team, only: [:new, :index, :create]
# GET /teams/1/memberships/new
def new
#students = Student.where.not(id: #team.students)
#membership = #team.memberships.new
end
# POST /teams/1/memberships
def create
#membership = #team.memberships.new(membership_params)
if #membership.save
redirect_to #team, notice: "Student added to team"
else
#students = Student.where.not(id: #team.students)
render :new
end
end
# DELETE /memberships/1
def destroy
#membership.destroy
redirect_to #membership.team, notice: "Student removed from team"
end
private
def set_team
#team = Team.find(params[:team_id])
end
def set_membership
#membership = Membership.find(params[:id])
end
def membership_params
params.require(:membership)
.permit(:student_id)
end
end
<%= form_with(model: #membership, local: true) do |f| %>
<div class="field">
<%= f.label :student_id %>
<%= f.collection_select :student_ids, #students, :id, :email %>
</div>
<%= f.submit %>
<% end %>
As a rule of thumb if you're creating a method on your controller thats not one of the standard CRUD methods and it contains a synonym to of one of them (add, remove, etc) you're almost certainly doing it wrong and should treat it as separate RESTful resource.

Create action by a has many through association to assign a favorite_category

Problem
I'm trying to create a middle table called category_profiles, is a intermediate table to assign favorite categories to my profiles, but I can't access to the category_ids, that I put in my form, always I got the same validation, Category doesn't exist:
Code:
class CategoryProfile < ApplicationRecord
belongs_to :profile
belongs_to :category
end
class Category < ApplicationRecord
has_many :category_profiles
has_many :profiles, through: :category_profiles
class Profile < ApplicationRecord
has_many :category_profiles
has_many :categories, through: :category_profiles
When I'm doing the create action, my controller can't find my category. How do I fix it?
My create action never find the ids of my categories to assign to the category_profiles. It has many through relation:
Module Account
class FavoritesController < Account::ApplicationController
before_action :set_category_profile
def index
#favorites = #profile.categories
end
def new
#categories = Category.all
#category_profile = CategoryProfile.new
end
def create
#category_profile = #profile.category_profiles.new(category_profile_params)
if #category_profile.save
flash[:success] = t('controller.create.success',
resource: CategoryProfile.model_name.human)
redirect_to account_favorites_url
else
flash[:warning] = #category_profile.errors.full_messages.to_sentence
redirect_to account_favorites_url
end
end
def destroy
end
private
def set_category_profile
#category_profile = CategoryProfile.find_by(params[:id])
end
def category_profile_params
params.permit(:profile_id,
category_ids:[])
end
end
end
Form
<%= bootstrap_form_with(model: #category,method: :post , local: true, html: { novalidate: true, class: 'needs-validation' }) do |f| %>
<div class="form-group">
<%= collection_check_boxes(:category_ids, :id, Category.all.kept.children.order(name: :asc), :id, :name, {}, { :multiple => true} ) do |b| %>
<%= b.label class: 'w-1/6 mr-4' %>
<%= b.check_box class: 'w-1/7 mr-4' %>
<%end %>
</div>
<div class="md:flex justify-center">
<%= f.submit 'Guardar categoría favorita', class: 'btn btn-primary' %>
</div>
<% end %>
Seems like you just want to update intermediate table. So you can do it like this.
def create
begin
#profile.categories << Category.find(params[:category_ids])
Or
params[:category_ids].each do |category_id|
#profile.category_profiles.create(category_id: category_id)
end
flash[:success] = t('controller.create.success',
resource: CategoryProfile.model_name.human)
redirect_to account_favorites_url
rescue
flash[:warning] = #category_profile.errors.full_messages.to_sentence
redirect_to account_favorites_url
end
end
Need to find other better way for error handling using either transaction block or something.

has_one and belongs to form

I'm rather new to rails and I'm stuck with this has_one and belongs_to form. I'm trying to create a team that has two speakers (from class 'User') through a form ,in the following manner:
class Team<ActiveRecord::Base
belongs_to :league
belongs_to :seed
has_many :speakers do
def user(level="1")
find_by(level: level).user
end
end
end
my user model looks like this :
class User < ActiveRecord::Base
belongs_to :team
end
user model:
class User
speaker model:
class Speaker < ActiveRecord::Base
belongs_to :team
belongs_to :user
end
my issue is (i think ) primarily in my controllers and form.controller looks like:
class TeamsController<ApplicationController
def new
#seed=Seed.find_by_id(params[:seed_id])
#league=current_admin.league
#team=current_admin.league.teams.build(:seed_id=>#seed,:approved=>false)
#usernames= #mca.connections.connected.each do |x| x.user end
end
def create
#league=current_admin.league
#team = #league.teams.build(team_params)
if #team.save
flash[:notice] = "Team Request Sent!."
redirect_to '/'
else
flash[:error] = "Unable to request team."
redirect_to :back
end
end
form looks like:
<div class="panel-body">
<div class="container">
<%= form_for #team do |f| %>
<%= f.hidden_field :seed_id, :value => #seed.id %>
<%= f.hidden_field :league_id, :value => #league.id %>
<div class="row">
<!-- <div class="col-md-8"> -->
<div class="form-group">
<%= f.collection_select :speaker, #usernames,:user,:fullname, multiple:true %>
</div>
<!-- </div> -->
</div>
<div class="actions">
<%= f.submit "Create" , class:"btn btn-primary" %>
</div>
<% end %>
</div>
</div>
I would really appreciate some help because it keeps throwing the following error:
NoMethodError in TeamsController#create
undefined method `each' for "2":String
The surface issue you have is that you're passing a string when Rails is expecting an object:
User(#69980837338020) expected, got String(#69980808947560)
This means you should be sending #user rather than "username" etc.
The error will likely be on this line:
#team = #league.teams.build team_params
... which means that you're passing :speaker (which Rails needs as an object) when you should be passing the speaker_id foreign key. Yury Lebedev's answer explains how to do this.
There is a deeper issue.
I don't see how each User can only belong to a Team:
class AddFieldsToUser < ActiveRecord::Migration
def change
add_column :users, :speaker_id, :integer
add_column :users, :speaker2_id, :integer
end
end
For this to work, your users can only be a member of one team.
Whilst this might work for a smaller scale product, I personally feel it to be an incorrect schema setup.
If anything, you'd expect the team to have speaker_1 and speaker_2, which would mean those two options being stored in the teams database (not user).
I think this is the cause of your problem (you're trying to set the speaker_1 and speaker_2 params when they don't exist in the teams db).
-
I would recommend the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :speaking_engagements, class_name: "Speaker"
has_many :teams, through: :speaking_engagements
end
#app/models/speaker.rb
class Speaker < ActiveRecord::Base
#columns team_id | user_id | level | created_at | updated_at
belongs_to :team
belongs_to :user
end
#app/models/team.rb
class Team < ActiveRecord::Base
has_many :speakers do
def user(level="1")
find_by(level: level).user
end
end
end
This will give you the ability to call:
#team = Team.find params[:id]
#speakers = #team.speakers
#user.speaking_engagements.where(team: #team)
To save it, you'll be able to use the following:
#app/controllers/teams_controller.rb
class TeamsController < ApplicationController
def new
...
#team = current_admin.league.teams.build seed: #seed, approved: false
end
def create
#league = current_admin.league
#team = #league.teams.build team_params
if #team.save
...
end
private
def team_params
params.require(:team).permit(:name, :speakers) #-> not sure about "speakers"
end
end
This should allow you to define the following:
#app/views/teams/new.html.erb
<%= form_for #team do |f| %>
<%= f.collection_select :speakers, #usernames, :id, :name, multiple: true %>
<%= f.submit %>
<% end %>

Create and update with nested models using strong parameters Rails

Here are my 3 models.
User
has_many :memberships
has_many :teams, through: :memberships, dependent: :destroy
accepts_nested_attributes_for :memberships
Team
has_many :memberships
has_many :users, through: :memberships, dependent: :destroy
accepts_nested_attributes_for :memberships
Membership
belongs_to :team
belongs_to :user
Here are some portions of my Team controller. My objective here is to add/update members to a certain team. Note that the source for adding members already exists as a group of users.
TeamsController
def create
#team = Team.new(team_params)
#team.users << User.find(member_ids) #add leader and members to team
if #team.save
#redirect to created team
else
#show errors
end
end
def update
#TO DO: update team roster here
if #team.update(team_params)
#redirect to updated team
else
#show errors
end
end
Strong parameters for Team controller
#parameters for team details
def team_params
params.require(:team).permit(:name, :department)
end
#parameters for members (includes leader)
def members_params
params.require(:team).permit(:leader, members:[])
end
#get id values from members_params and store in an array
def member_ids
members_params.values.flatten
end
For the form, I only have:
Name (text field)
Department (combo box)
Leader (combo box, with options generated depending on the selected department, submits as a selected user's user id)
Members (combo box, multiple, with options generated depending on the selected department, submits as an array of selected users' user ids)
I can successfully create a team, together with the passing of validations (both team and membership model), on my create. However, I have no idea on how to update the team, because if I use #team.users.clear and then simply do the same thing from create (I know, it's a bit stupid to do this), it will validate, but it will save it regardless if there's an error or not.
FORM CODE
<%= form_for(#team, remote: true) do |f| %>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
<%= f.label :department, "Department" %>
<%= f.select :department, options_for_select(["Architectural", "Interior Design"], department), include_blank: true %>
<%= f.label :leader, "Leader" %>
<%= f.select :leader, select_leaders(department, #team.id), {include_blank: true, selected: pre_select(:leader)} %>
<%= f.label :members, "Members" %>
<%= f.select :members, select_members(department, #team.id), {include_blank: true}, {id: "team_members", multiple: :multiple, data: {member_ids: pre_select(:members)}}%>
<% end %>
Note for the form:
This form works for both blank and populated forms.
The :members field is select2 enabled.
So my questions here are:
How can I update members of team? Is it possible to update based from what my strong parameters currently have, or do they need to be revised?
Should my create method be revised too?
SOME OPTIONS LEADING TO SOLUTION
Option #1 (best solution so far)
I only did a first-aid solution for this, so I think there's a better approach than what I did below. What I did here is to create users params with the users found from the member_ids as values.
TeamsController
def create
team = Team.new(team_params.merge({users: User.find(member_ids)}))
...
end
def update
...
if #team.update(team_params.merge({users: User.find(member_ids)}))
..
end
Option #2
Independent from solution 1, I only had team_params as strong parameter.
TeamsController
...
private
def team_params
params.require(:team).permit(:name, :department, :leader, members:[])
end
I created setter methods for both leader and members. But it seems that members overwrites the leader setter because I used the update method for both setters, and the update uses the same resource which is users. A workaround seems to be possible with this option.
Team
...
def leader=(leader_id)
#self.update(users: User.find(leader_id))
end
def members=(members_ids)
#self.update(users: User.find(members_id))
end
Since, leader and members are not so different in your scenario. You can change your models and view form to something like this:
class Team < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships, dependent: :destroy
accepts_nested_attributes_for :memberships
end
class User < ActiveRecord::Base
has_many :memberships
has_many :teams, through: :memberships, dependent: :destroy
end
class Membership < ActiveRecord::Base
belongs_to :team
belongs_to :user
accepts_nested_attributes_for :user
end
and form view code:
<%= form_for(#team) do |f| %>
<% if #team.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#team.errors.count, "error") %> prohibited this team from being saved:</h2>
<ul>
<% #team.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :memberships do |m| %>
<div class="field">
<%= m.label :memberships_name %><br>
<%= m.text_field :name %>
</div>
<%= m.fields_for :user do |u| %>
<div class="field">
<%= u.label :user_name %><br>
<%= u.text_field :name %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Also, please make sure you change this in your controller:
# GET /teams/new
def new
#team = Team.new
3.times do # number of members you need to generate!
#team.memberships.build{ |m| m.build_user }
end
end
# GET /teams/1/edit
def edit
end
# POST /teams
# POST /teams.json
def create
#team = Team.new(team_params)
respond_to do |format|
if #team.save
format.html { redirect_to #team, notice: 'Team was successfully created.' }
format.json { render action: 'show', status: :created, location: #team }
else
format.html { render action: 'new' }
format.json { render json: #team.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_team
#team = Team.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def team_params
params.require(:team).permit(:name, memberships_attributes: [:id, :name, user_attributes: [:id, :name]])
end
Although you can do this in Rails console to do a quick code validation:
team_params = {"name"=>"Team", "memberships_attributes"=>{"0"=>{"name"=>"Membership 1", "user_attributes"=>{"name"=>"User 1"}}, "1"=>{"name"=>"Membership 2", "user_attributes"=>{"name"=>"User 2"}}, "2"=>{"name"=>"Membership 3", "user_attributes"=>{"name"=>"User 3"}}}}
team = Team.new(team_params)
team.save
team.users
#=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 1, name: "User 1", email: nil, created_at: "2014-09-04 11:25:48", updated_at: "2014-09-04 11:25:48">, #<User id: 2, name: "User 2", email: nil, created_at: "2014-09-04 11:25:48", updated_at: "2014-09-04 11:25:48">, #<User id: 3, name: "User 3", email: nil, created_at: "2014-09-04 11:25:48", updated_at: "2014-09-04 11:25:48">]>
I hope it helps.

Error undefined method `categories' for #<User:0x00000103047730>

This happen in my app/controllers/categories_controller.rb:3:in `create'
I plan to develop a blog and each user can create fews categories for their micropost. Therefore each micropost can only has one category. I have 3 table :user, microposts and category. My intention is to have a user to add category at user profile page.
model/category.rb
class Category < ActiveRecord::Base
belongs_to :user
attr_accessible :category
end
model/user.rb
has_many :category, :dependent => :destroy
accepts_nested_attributes_for :category, :reject_if =>lambda {|a| a[:category].blank?}
categoriesController
class CategoriesController < ApplicationController
def create
#category = current_user.categories.new(params[:category])
if #category.save
flash[:success] = "Category created!"
redirect_to #user
else
flash[:error] = "Category not created."
render #user
end
end
end
usersController
def show
#user = User.find(params[:id])
#title = #user.name
#category = #user.category.new
end
user show.html
<%= form_for #category do |f|%>
<%= hidden_field_tag :user_id, #user.id %>
<%= f.label :category ,"Category:"%>
<%=h f.text_field :category %><br />
<%= f.submit "Add Category" %>
<% end %>
In User model you have relation
has_many :category
But in controller get from user categor ies
current_user.categories
Rename relation name to has_many :categories.

Resources