I am having trouble figuring out how to make some data collected through a nested model appear on the "show" page. I have a rails app with 3 models, a User model, a Project model, and a Team model. The model associations are as follows:
Project:-
class Project < ActiveRecord::Base
has_many :users, :through => :team
has_one :team, :dependent => :destroy
accepts_nested_attributes_for :team, allow_destroy: true
end
Team:-
class Team < ActiveRecord::Base
belongs_to :project
has_many :users
end
User:-
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_and_belongs_to_many :teams
end
Every project has one team, and every team consists of many users who are already saved in the database. What I would like to do exactly is to make it possible to select multiple existing users within the Project form (through a nested form) and save them to a model called Team. I managed to get the form working correctly, but im not sure how to go about saving the data collected to the team model, and then to make the group of users that were selected (the team) to appear in project's show page, as there are 3 models involved. The Please help!
P.S I used the nested form gem to add multiple team members within the project's form.
Projects Show page:-
<%= bootstrap_nested_form_for(#project, :html => {:multipart => true}, layout: :horizontal) do |f| %>
<% f.fields_for :teams do |builder| %>
<% if builder.object.new_record? %>
<%= builder.collection_select :user, User.all, :id, :email, { prompt: "Please select", :selected => params[:user], label: "Employee" } %>
<% else %>
<%= builder.hidden_field :_destroy %>
<%= builder.link_to_remove "Remove" %>
<% end %>
<%= f.link_to_add "Add Team Member", :teams %>
<%= f.submit %>
<% end %>
projects controller:-
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#projects = Project.all
respond_with(#projects)
end
def show
respond_with(#project)
end
def new
#project = Project.new
#project.pictures.build
#project.teams.build
respond_with(#project)
end
def edit
#project = Project.find(params[:id])
#project.pictures.build
#project.teams.build
end
def create
#project = Project.new(project_params)
if #project.save
flash[:notice] = "Successfully created project."
redirect_to #project
else
render :action => 'new'
end
end
def update
#project.update(project_params)
respond_with(#project)
end
def destroy
#project.destroy
respond_with(#project)
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:id, :title, :description, :status, :phase, :location, :image, pictures_attributes: [:id, :image], teams_attributes: [:project_id, :user_id])
end
end
Related
I am new in rails and programming at all. I have to add a kind of id_user_created and id_user_edited in a table called Plan. These ids will help me to know which user created and edited a plan, but I have no idea how to do it. On my db schema, there is no relation between User and Plan but now that I have to add theses ids, I assume that I will have to create a relation, right? Thanks a lot.
Models
class Plan < ApplicationRecord
has_many :users
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :plan
end
Controller
class PlansController < ApplicationController
def new
#plan = Plan.new
end
def create
#user = current_user
#plan = Plan.new(plan_params)
#plan.user = #user
if #plan.save
redirect_to plan_path(#plan), notice: 'O plano foi criado com sucesso.'
else
render :new
end
end
def edit
#plan = Plan.find(params[:id])
end
def update
#plan = Plan.find(params[:id])
if #plan.update(plan_params)
redirect_to #plan, notice: 'O plano foi editado com sucesso.'
else
render :edit
end
end
private
def plan_params
params.require(:plan).permit(:name, :duration, :price, :status, :default)
end
end
Route
Rails.application.routes.draw do
devise_for :users
root to: 'pages#home'
resources :plans do
resources :accounts, only: %i[new create] do
end
end
resources :payments, only: %i[index]
resources :accounts, only: %i[index show edit update destroy] do
resources :users, only: %i[new create] do
resources :roles
end
end
resources :users, only: %i[index show edit update destroy]
Plans Form
<%= simple_form_for [#user, #plan] do |f| %>
<%= f.input :name, label: 'Nome' %>
<%= f.input :duration, label: 'Duração' %>
<%= f.input :price, label: 'Preço' %>
<%= f.input :status %>
<%= f.input :default %>
<%= f.button :submit, class:"btn-outline-secondary" %>
<% end %>
In Rails you generate migrations to create foreign keys by using the references (aka belongs_to) type:
rails g migration add_user_to_plans user:references
Which generates the following migration:
class AddUserToPlans < ActiveRecord::Migration[6.0]
def change
add_reference :plans, :user, null: false, foreign_key: true
end
end
When you run the migration it creates a plans.user_id column which points to the users table.
If you want to call the column/association something else like creator_id you need to explicitly tell rails which table you are referencing. Just don't call your columns id_user_created unless you want to come off as a complete snowflake.
class AddCreatorToPlans < ActiveRecord::Migration[6.0]
def change
add_reference :plans, :creator,
null: false,
foreign_key: { to_table: :users }
end
end
And you also have to explicitly set up your association:
class Plan
belongs_to :creator,
class_name: 'User',
inverse_of: :plans
end
class User
has_many :plans,
foreign_key: :creator_id,
inverse_of: :plans
end
Your form is also off. When you're dealing with creating resources as the logged in user you don't want/need to nest the route.
<%= simple_form_for #plan do |f| %>
<%= f.input :name, label: 'Nome' %>
<%= f.input :duration, label: 'Duração' %>
<%= f.input :price, label: 'Preço' %>
<%= f.input :status %>
<%= f.input :default %>
<%= f.button :submit, class:"btn-outline-secondary" %>
<% end %>
And you can also trim that create method down by building the resource off the association on the current user:
def create
#plan = current_user.plans.new(plan_params)
if #plan.save
redirect_to #plan,
notice: 'O plano foi criado com sucesso.'
else
render :new
end
end
While you could do the same thing and add an editor_id column to plans its probably not what you want as it will only let you record a single id and not something more useful like a history of who edited the record and when which requires a join table and this is really an entire question on its own.
**Try add 's' to model in database relationship**
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
belongs_to :plans
end
<%= simple_form_for [#user, #user.plans] do |f| %>
<%= f.input :name, label: 'Nome' %>
<%= f.input :duration, label: 'Duração' %>
<%= f.input :price, label: 'Preço' %>
<%= f.input :status %>
<%= f.input :default %>
<%= f.button :submit, class:"btn-outline-secondary" %>
<% end %>
I'm trying to setup a simple rails app with job board functionality. I was able to add jobs to the database, until I added an association between my Job model and devise User model. Now it won't update the database when I fill out the form.
jobs_controller
class JobsController < ApplicationController
def index
#jobs = Job.all
end
def new
#job = Job.new
end
def listing
end
def listings
end
def create
#job = Job.new(params.require(:job).permit(:title, :description, :url, :user_id))
if #job.save
redirect_to root_path
else
render "new"
end
end
end
new.html.erb
<%= simple_form_for #job do |form| %>
<%= form.input :title, label: "Job title" %>
<%= form.input :description, label: "Description" %>
<%= form.input :url, label: "URL" %>
<%= form.button :submit %>
<% end %>
index.html.erb
<% #jobs.each do |job| %>
<div class="job">
<h2><%= link_to job.title, job.url %></h2>
<p><%= job.description %></p>
</div>
<% end %>
<p><%= link_to "Add a job", new_job_path %></p>
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :jobs
end
job.rb
class Job < ApplicationRecord
belongs_to :user
end
There isn't an error in the console, but the database doesn't seem to be updated or it's not updating the view.
I also ran a migration:
class AddUserToJob < ActiveRecord::Migration[5.2]
def change
add_reference :jobs, :user, foreign_key: true
end
end
You can get the user with current_user in Devise.
class JobsController < ApplicationController
# This restricts the actions to authenticated users and prevents a nil error
before_action :authenticate_user, except: [:show, :index]
# ...
def create
# this sets the user_id column
#job = current_user.jobs.new(job_params)
if #job.save
# you really should set a flash message or something to notify the user
# and possibly redirect to the show or index action instead
redirect_to root_path
else
render "new"
end
end
private
def job_params
params.require(:job)
.permit(:title, :description, :url, :user_id)
end
end
If you don't want to associate the job immediately to a user, you need to change the association to be optional, like:
class Job < ApplicationRecord
belongs_to :user, optional: true
end
Else you need to supply user_id in your form or set it in the controller action.
You should also delegate this part to a separate method
def job_params
params.require(:job).permit(:title, :description, :url, :user_id)
end
Job.new(job_params)
I've got two models User and Image as polymorphic association because I want my image model to reuse in other models.
class User < ApplicationRecord
has_one :cart
has_many :images, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :images
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
before_validation :set_name, on: :create
validates :name, presence: true
private
def set_name
self.name = "person#{rand(1000)}" if self.name.blank?
end
end
class Image < ApplicationRecord
mount_uploader :image, ImageUploader
belongs_to :imageable, polymorphic: true
end
And I made Image polymorphic: true and use carrierwave gem for creating uploader `mount_uploader mount_uploader :image, ImageUploader in Image model:image
class ImageUploader < CarrierWave::Uploader::Base
end
and I permit :image parameters to each model: User and Good,
module Admin
class UsersController < BaseController
before_action :set_admin_user, only: [:show, :edit, :update, :destroy]
def users_list
#admin_users = User.all.preload(:images).where(admin: true)
end
def show
end
def edit
end
def update
if #user.images.update(admin_user_params)
redirect_to admin_users_list_path, notice: 'User was successfully updated'
else
flash[:alert] = 'User was not updated'
end
end
def destroy
end
private
def set_admin_user
#user = User.find(params[:id])
end
def admin_user_params
params.require(:user).permit(:name, :email, images_attributes: [:image])
end
end
end
In my view form I've got the next code:
<%= form_for [:admin, #user], html: { multipart: true } do |f| %>
<%= f.label 'Name', class: 'form-group' %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.fields_for :images_attributes do |i| %>
<%= i.label :image %>
<%= i.file_field :image %>
<% end %>
<%= f.label 'Email', class: 'form-group' %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.submit class: 'btn btn-oultline-primary' %>
<% end %>
but when I want to update user for exampletry to upload the image I've got the next:
Here is what I have as response
I can't saveupload my image. Why is that? I expect to have an insert into db but it doesn't happen and in db I've got no attached images.
Since you are adding multiple images, change your form to:
<%= i.file_field :image, multiple: true, name: "images_attributes[image][]" %>
And in the controller:
def edit
#image = #user.images.build
end
def update
if #user.images.update(admin_user_params)
create_user_images
redirect_to admin_users_list_path, notice: 'User was successfully updated'
else
flash[:alert] = 'User was not updated'
end
end
private
def admin_user_params
params.require(:user).permit(:name, :email, images_attributes: [:id, :user_id, :image])
end
def create_user_images
if params[:images_attributes]
params[:images_attributes]['image'].each do |i|
#image = #user.images.create!(:image => i)
end
end
end
Let me know if you still have problems after the edits :)
I cannot figure out why #comments is returning nil when I am attempting to loop through it. If I use #event.comments.each do instead it works just fine. My current structure is User / Events / Comments.
Comments Controller:
class CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
def create
#event = Event.find(params[:event_id])
#comment = #event.comments.create(comment_params)
#comment.user = current_user
if #comment.save
flash[:notice] = "Comment Added"
redirect_to #event
else
flash[:alert] = "Comment Not Added"
redirect_to #event
end
end
def show
#event = Event.find(params[:id])
#comments = #event.comments
end
def destroy
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
Events Controller Show Action:
class EventsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create,:edit, :update, :show,
:index, :destroy]
def show
#event = Event.find(params[:id])
end
private
def event_params
params.require(:event).permit(:start_date, :start_time, :location, :title, :description, :size, :difficulty,
:activity, :duration)
end
end
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :event
belongs_to :user
validates :body, presence: true
scope :newest, -> { order("created_at DESC") }
end
User Model:
class User < ActiveRecord::Base
has_many :created_events, class_name: 'Event', :foreign_key => "creator_id",
dependent: :destroy
has_many :registers, :foreign_key => "attendee_id", dependent: :destroy
has_many :attended_events, through: :registers, dependent: :destroy
has_many :comments, through: :events
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable
validates :name, presence: true, uniqueness: true, length: { maximum: 50 }
validates :email, presence: true, uniqueness: { case_sensitive: true }
validate :validate_name
def validate_name
if User.where(email: name).exists?
errors.add(:name, :invalid)
end
end
end
Event Model:
class Event < ActiveRecord::Base
scope :latest, -> { order(date: :asc, time: :asc)}
belongs_to :creator, class_name: 'User'
has_many :registers, :foreign_key => 'attended_event_id', dependent: :destroy
has_many :attendees, through: :registers, dependent: :destroy
has_many :comments, dependent: :destroy
validates :title, presence: true, length: { maximum: 50 }
validates :description, presence: true, length: { maximum: 500 }
validates :location, presence: true
validates :start_time, presence: true
validates :start_date, presence: true
validates :activity, presence: true
validates :difficulty, presence: true
end
and lastly, the comments/_show.html.erb partial:
<% if #comments %>
<span class="results-number color-aqua-show">Comments</span>
<% #comments.each do |comment| %>
<p class="comments">
<i class="color-green fa fa-user ride-i"></i>
<%= comment.user.name %>: <%= time_ago_in_words(comment.created_at).capitalize %> ago
</p>
<p>
<i class="color-aqua fa fa-comment ride-i"></i>
<%= comment.body %>
</p>
<div class="bottom-breaker"></div>
<% end %>
<% else %>
<span class="results-number color-aqua-show">Be the first to comment!</span>
<% end %>
Show form from events:
<div class="container s-results margin-bottom-50">
<div class="row">
<div class="col-md-9">
<%= render partial: 'comments/show' %>
<%= render partial: 'comments/form' %>
</div>
</div>
</div>
Again, if I change #comments in the partial to #events.comments it will recognize that there are comments for the particular event and loop through them. This has been driving me insane for the better part of 5 hours now. Thanks.
As Pardeep Saini said, you need to add #comments to events#show:
def show
#event = Event.find params[:id]
#comments = #event.comments
end
The problem is that #comments is a variable, which needs to be defined. If it isn't defined, then you're going to receive the equivalent of an undefined error.
Thus, to fix it, you need to make sure that you're calling a defined variable; either #comments (if you've defined it), or #event.comments.
I think there is a much deeper issue with your structure (from looking at your code).
You'd be better setting it up like this:
#config/routes.rb
resources :events do
resources :comments, only: [:create, :destroy] #-> url.com/events/:event_id/comments...
end
#app/controllers/comments_controller.rb
class EventsController < ApplicationController
def show
#event = Event.find params[:id]
#comments = #event.comments
end
end
This will allow you to use the following:
#app/views/events/show.html.erb
<%= #event.title %>
<%= render #comments %>
<%= render "new_comment" %>
#app/views/events/_comment.html.erb
<%= comment.user.name %>: <%= time_ago_in_words(comment.created_at).capitalize %> ago
<%= comment.body %>
#app/views/events/_new_comment.html.erb
<%= form_for #event.comments.build do |f| %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
This will make it so that if you browse to url.com/events/1, it will output all the event's comments.
The added benefit of this setup is the ability to create / destroy comments:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :set_event
def create
#comment = #event.comments.new comment_params
#comment.user = current_user
#comment.save
end
def destroy
#comment = #event.comments.find params[:id]
#comment.destroy
end
private
def comment_params
params.require(:comment).permit(:body, :user)
end
def set_event
#event = Event.find params[:event_id]
end
end
Solved the problem. It was a very dumb error where I had show listed twice in my events controller. The bottom one was over riding the top.
I am having trouble figuring out how to make some data collected through a nested model appear on the "show" page. I have a rails app with 3 models, a User model, a Project model, and a Team model. The model associations are as follows:
Project:-
class Project < ActiveRecord::Base
has_many :users, :through => :team
has_one :team, :dependent => :destroy
accepts_nested_attributes_for :team, allow_destroy: true
end
Team:-
class Team < ActiveRecord::Base
belongs_to :project
has_many :users
end
User:-
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_and_belongs_to_many :teams
end
Every project has one team, and every team consists of many users who are already saved in the database. What I would like to do exactly is to make it possible to select multiple existing users within the project form (through a nested form) and save them to a model called team. I managed to get the form working correctly, but im not sure how to go about saving the data collected to the team model, and then to make the group of users that were selected (the team) to appear in project's show page. Please help!
P.S I used the nested form gem to add multiple team members within the project's form.
Projects Show page:-
<%= bootstrap_nested_form_for(#project, :html => {:multipart => true}, layout: :horizontal) do |f| %>
<% f.fields_for :teams do |builder| %>
<% if builder.object.new_record? %>
<%= builder.collection_select :user, User.all, :id, :email, { prompt: "Please select", :selected => params[:user], label: "Employee" } %>
<% else %>
<%= builder.hidden_field :_destroy %>
<%= builder.link_to_remove "Remove" %>
<% end %>
<%= f.link_to_add "Add Team Member", :teams %>
<%= f.submit %>
<% end %>
projects controller:-
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#projects = Project.all
respond_with(#projects)
end
def show
respond_with(#project)
end
def new
#project = Project.new
#project.pictures.build
#project.teams.build
respond_with(#project)
end
def edit
#project = Project.find(params[:id])
#project.pictures.build
#project.teams.build
end
def create
#project = Project.new(project_params)
if #project.save
flash[:notice] = "Successfully created project."
redirect_to #project
else
render :action => 'new'
end
end
def update
#project.update(project_params)
respond_with(#project)
end
def destroy
#project.destroy
respond_with(#project)
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:id, :title, :description, :status, :phase, :location, :image, pictures_attributes: [:id, :image], teams_attributes: [:project_id, :user_id])
end
end