#The various models
class Team < ActiveRecord::Base
has_many :competition_teams
has_many :competitions, through: :competition_teams
end
class Competition < ActiveRecord::Base
has_many :competition_teams
has_many :teams, through: :competition_teams
end
class CompetitionTeam < ActiveRecord::Base
belongs_to :team
belongs_to :competition
end
#The form I'm using to add teams to the competition
= semantic_form_for #competition do |f|
= f.inputs :name do
= f.input :teams, as: :select, collection: current_user.teams.select{|t| !#competition.teams.include?(t)}
= f.actions do
= f.action :submit, as: :button
#Competition update action, used to add teams
def update
#competition = Competition.find(params[:id])
teams = competition_params[:team_ids] + #competition.teams.pluck(:id)
team = Team.find(competition_params[:team_ids][1])
if team.users.pluck(:id).include?(current_user.id) && #competition.update_attribute(:team_ids, teams)
redirect_to #competition
end
end
So what I want to do is to create a button (or a link) that allows a user to remove their team from the competition. Should this be done with a custom action or a form of some sort?
I really have no idea where to go from here, so any help is very appreciated
By default, a form_for will make a post request and goes to the create action if the object is a new object, and to the update action if the object is already in the db. What you want to have it do is make a request to the delete action where you will be deleting the team from the competition. You should get the #competition_team object first.
#competition_team = CompetitionTeam.new
then
= semantic_form_for #competition_team, method: 'delete' do |f|
Then in your competition_team controller, create your destroy action along with your code to delete the team from the competition.
def destroy
#your code
end
Also, make sure to define the destroy action in your route.
Related
I am currently learning rails and is attempting to create a Survey management application. The application have the following models:
class Survey < ApplicationRecord
has_many :survey_questions
has_many :questions, through: : survey_questions
accepts_nested_attributes_for :questions
end
class SurveyQuestion < ApplicationRecord
belongs_to :survey
belongs_to :question
end
class Question < ApplicationRecord
has_many :survey_questions
has_many :surveys, through: :survey_questions
end
I want to be able to automatically associate existing questions ID to be used instead of creating a whole new list of questions every time I create a new Survey.
I found this solution which was close to what I want accepts_nested_attributes_for with find_or_create? . It is able to associate my records correctly, but at the end of the transaction it will always create a new record at Question model regardless.
Can anyone suggest why is this happening and help point me to the right direction? Thank you!
EDIT
Below is the code for my create, new and survey_params method:
def new
#survey = Survey.new
#questions = Question.all
unless #question.blank?
#questions.each do |question|
#survey.questions.build(name: question)
end
end
end
def create
#survey = Survey.new(survey_params)
#survey.save!
end
def survey_params
params.require(:survey).permit(:enddate, questions_params: [:name])
end
Below is my view for new and questions_fields, I am using cocoon gem to help me to help generate them dynamically:
new
= simple_form_for #survey, url: survey_index_path, method: :post do |f|
= f.input :finaldate, as: :string
= f.simple_fields_for :questions do |qn|
= render 'question_fields', f: qn
= link_to_add_association 'New Question', f, :questions
question_fields
= f.input :name
= link_to_remove_association 'Remove Question', f
I can't wrap my head around this any help would be appreciated. I went through many articles and other postings on here and I can't seem to get the results I'm looking for.
I have a User model and Team model.
Team Model has user_id and team_id
The user who creates the team will be user_id and users who are members of the team will be in the team_id
User Model
has_many :teams, foreign_key: :team_id
has_many :team_members, class_name: "Team", foreign_key: :user_id
Team Model
belongs_to :user, foreign_key: :team_id
belongs_to :team_member, class_name: "User", foreign_key: :user_id
The end result of what I'm trying to achieve is:
Each user can add as many team members
Each user can see a list of users who are part of their team.
A view where, Users who are part of a team can see which team they are part of.
I believe what you're looking for is a join table model. The issue is that both a User and a Team may have many of each other. So the relationship must be stored separately.
See this answer here on a similar question: https://stackoverflow.com/a/15442583/5113832
So you might choose a model structure of User, Team and TeamMembership.
Updated to destroy dependent team memberships when a user or team is destroyed.
#app/models/user.rb
class User < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :teams, :through => :team_memberships
end
#app/models/team.rb
class Team < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :users, :through => :team_memberships
end
#app/models/team_membership.rb
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
Updated to reply to question:
How would my controller look on create? (Adding a user to a team) – Michael
In order to add a user to to a team you COULD do it in UserController, you COULD do it in TeamController. However, because you are now creating a TeamMembership resource you would want to create a TeamMembership record in a TeamMembershipsController. This keeps with the "Rails" way of doing things. So for example:
# app/controllers/team_memberships_controller.rb
class TeamMembershipsController < ApplicationController
def index
#team_memberships = TeamMembership.all
end
def new
#team_membership = TeamMembership.new
end
def create
#team_membership = TeamMembership.new(team_membership_params)
if #team_membership.save
flash[:success] = 'Team membership created'
redirect_to team_memberships_path
else
flash[:error] = 'Team membership not created'
render :new
end
end
def destroy
#team_membership = TeamMembership.find_by_id(params[:id])
if #team_membership && #team_membership.destroy
flash[:success] = 'Team membership destroyed'
else
flash[:error] = 'Team membership not destroyed'
end
redirect_to team_memberships_path
end
private
def team_membership_params
params.require(:team_membership).permit(
:user_id,
:team_id
)
end
end
The advantage to having the TeamMembership resource is using this pattern to manage when a user is added (#create), or removed (#destroy) from a team.
The magic of Rails associations will take care of accessing a team's members (users) and a user's teams for each instance of those models.
You just go about your business doing CRUD on these resources and Rails takes care of the organization for you by your conforming to it's conventions.
Also I updated my original model code to destroy team memberships when a user or team is destroyed. This ensures no orphaned records are in your team_memberships table.
As a final note. You should also be able to easily use form_for to send a TeamMembership to your controller to be created. This could be done using select option dropdowns for users and teams with Rails' collection_select:
<%# app/views/team_memberships/new.html.erb %>
<h1>
Create Team Membership
</h1>
<%= form_for(#team_membership) do |f| %>
<%= f.label(:user) %>
<%= f.collection_select(
:user_id,
User.all,
:id,
:username
) %>
<%= f.label(:team) %>
<%= f.collection_select(
:team_id,
Team.all,
:id,
:name
) %>
<%= f.submit %>
<% end %>
The above code will render dropdown for all users and teams allowing you to select a specific combination to create a team membership from. Deleting a team membership is as easy as sending a DELETE #destroy request with the id of the team membership.
Another consideration might be adding a unique pair constraint to your database table and model within the migration and model validations.
Hope this helps!
I have a query that adds a Customer to a certain Category. It is currently in the view template, and while it works, there's a bug - the customer is added to the category upon loading the page, before they have clicked the button.
I thought that moving that logic back to the controller might solve it (using form_for), then rendering the submit button in the view.
What do you think? And how would one implement it using form_for?
= link_to "JOIN CATEGORY NOW", root_path(#product.category.add_customer(current_customer)), class: "button4"
Edit:
Category Model
class Category < ActiveRecord::Base
#Associations
belongs_to :product
has_many :customer_categories
has_many :customers, through: :customer_categories
def add_customer(customer_id)
if customer = Customer.where(id: customer_id).first
self.customers << customer unless self.customers.include?(customer)
end
end
end
Product Model
class Product < ActiveRecord::Base
include ActionView::Helpers
#Callbacks
after_create do
Category.create product: self
end
#Associations
has_one :category, dependent: :destroy
Customer Model
class Customer < ActiveRecord::Base
#Associations
has_many :customer_categories
has_many :categories, through: :customer_categories
EDIT #2:
ActionView::Template::Error (undefined method `add_customer_category' for #<#<Class:0x007fcaebdbb5b0>:0x007fcae377ab90>):
routes:
resources :categories do
member do
get 'add_customer', to: 'categories/add_customer'
end
end
Categories Controller:
def add_customer
#product = Product.find(params[:id])
#product.category.add_customer(current_customer.id)
end
The customer is added to the category upon loading the page is happening because of this line:
= link_to "JOIN CATEGORY NOW", root_path(#product.category.add_customer(current_customer)), class: "button4"
Or more specifically this part of the code:
#product.category.add_customer(current_customer)
Since, when you load the page the Ruby code in view gets evaluated and hence the above code gets executed and customer gets added to the category.
Solution:
In your routes.rb:
resources :categories do
member do
get 'add_customer'
end
end
Now, in your view:
= link_to "JOIN CATEGORY NOW", add_customer_category_path(#product.category)
In your CategoriesController:
def add_customer
#category = Category.find(params[:id])
if #category.add_customer(current_customer)
redirect_to root_path
else
# Redirect user to an error page, maybe?
end
end
I have 3 models: Employers, Partners and Collaborations.
As an Employer, I want to add a record to my Partner model and to my Collaboration model to be able to indicate a collaboration between a Partner and a Employer. I therefore have the following columns in my database/tabels.
Models
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :partners, :through => :collaborations
end
class Partner < ActiveRecord::Base
has_many :collaborations
has_many :employers, :through => :collaborations
accepts_nested_attributes_for :collaborations
end
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
end
Tables
Collaborations
employer_id:integer
partner_id:integer
tarive:string
Partners
added_by:integer
name:string
Because I want to be able to add a Partner/Collaboration within 1 form, I use nested forms. So I can add a partner (name, etc) and a collaboration (tarive, etc) in one go.
My (simple_form) form looks like this (I have named_space resource).
Te reduce clutter, I removed as much HTML mark_up as I could, this is not the issue.
Form
/views/employer/partners/_form
= simple_form_for [:employer, #partner], html: { multipart: true } do |f|
Partner
= f.input :name, input_html: { class: 'form-control' }
= f.simple_fields_for :collaborations do |ff|
Tarive
= ff.input :tarive, input_html: { class: 'form-control' }
= f.button :submit, "Save"
My controller looks like
class Employer::PartnersController < ActionController::Base
def new
#partner = Partner.new
#partner.collaborations.build
end
def create
#partner = Partner.new(partner_params)
#partner.collaborations.build
#partner.added_by = current_employer.id
#partner.collaborations.employer_id = current_employer.employer_id
#partner.collaborations.partner_id = #partner.id
#partner.collaborations.added_by = current_employer.id
if #partner.save
redirect_to employer_partner_path(#partner), notice: "Succes!"
else
render 'new'
end
end
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:id, :employer_id, :partner_id, :tarive])
end
end
Problem
The problem/question I have is this. The attributes are assigned nicely and added in the model. But I want to add a employer_id as well, which I have in current_employer.employer.id (Devise). I do not want to work with hidden forms, just to avoid this issue.
I assigned 'parent' models always like #partner.added_by = current_employer.id and that works beautifully.
When I use:
#partner.collaborations.employer_id = current_employer.employer_id
I get an error, saying #partner.collaborations.employer_id is empty.
Question
How can I assign a variable to the nested_form (Collaboration) in my controller#create?
Or more specifically: how can I assign current_employer.employer_id to #partner.collaborations.employer_id?
There are several ways:
Merge the params
Deal with objects, not foreign keys
Personally, I feel your create method looks really inefficient. Indeed, you should know about fat model skinny controller - most of your associative logic should be kept in the model.
It could be improved using the following:
#app/controllers/employers/partners_controller.rb
class Employers::PartnersController < ApplicationController
def new
#partner = current_employer.partners.new #-> this *should* build the associated collaborations object
end
def create
#partner = current_employer.partners.new partner_params
#partner.save ? redirect_to(employer_partner_path(#partner), notice: "Succes!") : render('new')
end
private
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:tarive]) #when dealing with objects, foreign keys are set automatically
end
end
This would allow you to use:
#app/views/employers/partners/new.html.erb
= simple_form_for #partner do |f| #-> #partner is built off the current_employer object
= f.input :name
= f.simple_fields_for :collaborations do |ff|
= ff.input :tarive
= f.submit
... and the models:
#app/models/partner.rb
class Partner < ActiveRecord::Base
belongs_to :employer, foreign_key: :added_by
has_many :collaborations
has_many :employers, through: :collaborations
accepts_nested_attributes_for :collaborations
end
#app/models/collaboration.rb
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
belongs_to :creator, foreign_key: :added_by
before_create :set_creator
private
def set_creator
self.creator = self.employer_id #-> will probably need to change
end
end
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :employers, through: :collaborations
end
This may not give you the ability to set tarive, however if you cut down the manual declarations in your model, we should be able to look at getting that sorted.
The main thing you need to do is slim down your code in the controller. You're being very specific, and as a consequence, you're encountering problems like that which you mentioned.
I'm trying to build a form_for to create a join model between two other models. I have a Book model and User model, with another called Reads that is my join. Here is how I've set up the associations:
class User < ActiveRecord::Base
has_many :reads
has_many :books, :through => :reads
end
class Book < ActiveRecord::Base
has_many :reads
has_many :users, :through => :reads
end
class Read < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
I've looked at the docs for form_for and watched the railscast episode on many-to-many associations, but I can't figure out why I'm getting the error when I try to render the Book#show view where I've put the form:
First argument in form cannot contain nil or be empty
Here is my form in app/views/books/show.html.erb:
<%= form_for(#read) do |f| %>
<%= f.hidden_field :book_id, value: #book.id %>
<%= button_to 'Add to Reads', {controller: 'reads', action: 'create'}, {class: 'btn'} %>
<% end %>
I think part of the problem is that I am trying to create a 'Reads' object from the Books model, but I'm not sure what I am doing wrong. I need the 'Add to Reads' button on the Book's page so that a user can select that particular book to add to their 'reads.' I'm also adding the current_user id in the controller, rather than in the view. Here is my create action from the Reads controller if that helps...
def create
#read = Read.new(read_params)
#read.user_id = current_user.id
#read.save
if #read.save
# do this
else
# do that
end
end
And I'm using strong params...
def read_params
params.require(:read).permit(:user_id, :book_id)
end
Thanks for any help.
First argument in form cannot contain nil or be empty
This means that #read in your form is nil. Since you are in the show action of your Books controller, you have to define this variable in the books controller.
def show
#read = Read.new
...
end