Rails associations and passing another model :id as an argument - ruby-on-rails

I have two associated models: Job and Candidate. The Candidate model is updated when a user applies to a job listing through the candidates 'new' view. Since I've associated Candidate with the Job model, I can't save any new instances of Candidate. I know that I have to pass job_id as a parameter so that any new instance of Candidate references a particular Job, but I just cant figure out what I'm doing wrong. Also, the Job model is associated with the Devise User model, but Candidate is not associated with the User model. Could that be the issue? Thanks in advance.
I've tried a ton of things in my candidates controller, but now I've put it back to how it was before I associated the Candidate model with Job model, so I can start over again as if I just ran the migration to AddJobToCandidate
candidates_controller.rb
class CandidatesController < ApplicationController
def index
#candidates = Candidate.all
end
def new
#candidate = Candidate.new
render layout: false
end
def single
#candidate = Candidate.find(params[:id])
end
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
redirect_to root_path
else
render "new"
end
end
private
def candidate_params
params.require(:candidate)
.permit(:first_name, :last_name, :email, :cover_letter, :ph, :job_id)
end
end
views/candidates/new.html.erb
<h1>Work at <%= current_user.company %></h1>
<%= simple_form_for #candidate do |form| %>
<%= form.input :first_name, label: "First Name" %>
<%= form.input :last_name, label: "Last Name" %>
<%= form.input :email, label: "Email" %>
<%= form.input :cover_letter, label: "Cover Letter" %>
<%= form.input :ph, label: "Phone Number" %>
<%= form.button :submit %>
<% end %>
routes.rb
Rails.application.routes.draw do
get 'candidates/index'
get 'candidates/new/:id' => 'candidates#new', :as => 'apply'
get 'candidates/single/:id' => 'candidates#single', :as => 'viewcandidate'
get 'jobs/index'
get 'jobs/new'
get 'jobs/listing/:id' => 'jobs#listing', as: 'listing'
get 'jobs/listings', :as => 'careerpage'
get 'jobs/dashboard'
get 'jobs/single/:id' => 'jobs#single', :as => 'viewjob'
devise_for :users, controllers: { sessions: "users/sessions" }
resources :candidates
resources :jobs
root 'jobs#dashboard'
end
views/jobs/listing.html.erb the only view that links to candidates/new.html/erb
<h1><%= #job.title %></h1>
<p><%= #job.description %></p>
<p><%= link_to "Apply", apply_path(#job.id) %></p>
Candidate/Job association migration
class AddJobToCandidate < ActiveRecord::Migration[5.2]
def change
add_reference :candidates, :job, foreign_key: true
end
end
jobs_controller.rb (don't think this is necessary but I'll include anyway)
class JobsController < ApplicationController
before_action :authenticate_user!
def index
#jobs = current_user.jobs.all
end
def dashboard
end
def new
#job = Job.new
end
def listing
#job = Job.find(params[:id])
render layout: false
end
def listings
#jobs = current_user.jobs.all
render layout: false
end
def single
#job = Job.find(params[:id])
#candidates = Candidate.all
end
def create
#job = current_user.jobs.new(job_params)
if #job.save
redirect_to jobs_index_path
else
render "new"
end
end
private
def job_params
params.require(:job)
.permit(:title, :description, :url, :user_id)
end
end
No error is shown, but model is not updated.
One of the things I tried in candidate_controller #create action was:
def create
#candidate = #job.candidates.create(candidate_params)
if #candidate.save
redirect_to root_path
else
render "new"
end
end
and the error I get is 'undefined method `candidates' for nil:NilClass'
Another thing I tried was adding this line in the #create action:
#job = Job.find(params[:id])
and the error I get is 'Couldn't find Job without an ID'

When you arrive at candidate#new you’re just discarding the Job ID. You’re not making it part of the data submitted by the form to be picked up in the create controller.
Given you have completely abandoned RESTful routes, the quickest solution would be to add this to the form:
<%= form.hidden_field :job_id, value: params[:id] %>
However, I highly recommend you look at RESTful routes and nested resources. You have a mess where the ID passed to Candidate#new is the ID of a different model. While you’re at it you may want to reconsider having a candidate belong to a job, what happens when a candidate is interested in multiple jobs? They create a new profile each time? In job sites one would normally have a Job, a Candidate (or User / Profile) and then a JobApplication, which joins the two.

Related

Saving a list of emails from a form-text input into Models email_list attribute (array)

My goal is to when adding a new product with the new product form, to have an input where one can add a list of emails separated by a space. The list of emails in this string field would be saved as an array of emails in the email_list array attribute of the Product model. This way each product has many emails. (later an email will be sent to these users to fill out questionaire, once a user fills it out there name will be taken off this list and put on completed_email_list array.
I am relatively new to rails, and have a few questions regarding implementing this. I am using postgresql, which from my understanding I do not need to serialize the model for array format because of this. Below is what I have tried so far to implement this. These may show fundamental flaws in my thinking of how everything works.
My first thinking was that I can in my controllers create action first take params[:email].split and save that directly into the email_list attribute (#product.email_list = params[:email].split. It turns out that params[:email] is always nil. Why is this? (this is a basic misunderstanding I have)(I put :email as accepted param).
After spending a long time trying to figure this out, I tried the following which it seems works, but I feel this is probably not the best way to do it (in the code below), which involves creating ANOTHER attribute of string called email, and then splitting it and saving it in the email_list array :
#product.email_list = #product.email.split
What is the best way to actually implement this? someone can clear my thinking on this I would be very grateful.
Cheers
Products.new View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.input :email %>
<%= f.button :submit %>
<%end %>
Products Controller
class ProductsController < ApplicationController
before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
if params[:category].blank?
#products= Product.all.order("created_at DESC")
else
#category_id=Category.find_by(name: params[:category]).id
#products= Product.where(:category_id => #category_id).order("created_at DESC")
end
end
def new
#product=current_user.products.build
#categories= Category.all.map{|c| [c.name, c.id]}
end
def show
end
def edit
#categories= Category.all.map{|c| [c.name, c.id]}
end
def update
#product.category_id = params[:category_id]
if #product.update(product_params)
redirect_to product_path(#product)
else
render 'new'
end
end
def destroy
#product.destroy
redirect_to root_path
end
def create
#product=current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = #product.email.split
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:product).permit(:title, :description, :category_id, :video, :thumbnail,:email, :email_list)
end
def find_product
#product = Product.find(params[:id])
end
end
To solve your original issue
#product.email_list = params[:email].split. It turns out that params[:email] is always nil
:email is a sub key of :product hash, so it should be:
#product.email_list = params[:product][:email].split
Demo:
params = ActionController::Parameters.new(product: { email: "first#email.com last#email.com" })
params[:email] # => nil
params[:product][:email] # => "first#email.com last#email.com"
I'd say that what you have is perfectly fine, except for the additional dance that you're doing in #product.email_list=#product.email.split, which seems weird.
Instead, I'd have an emails param in the form and an #emails= method in the model (rather than email and #email=):
def emails=(val)
self.email_list = val.split
end
Alternatively, you could do that in the controller rather than having the above convenience #emails= method, similar to the way you're handling the category_id:
#product = current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = product_params[:emails].split
Because you need validations on your emails and to make it cleaner I would create an email table, make Product table accept Email attribues and use cocoon gem to have a nice dynamic nested form with multiple emails inputs.
1) models
class Product < ActiveRecord::Base
has_many :emails, dependent: :destroy
accepts_nested_attributes_for :emails, reject_if: :all_blank, allow_destroy: true
end
class Email < ActiveRecord::Base
belong_to :product
validates :address, presence: true
end
2) Controller
class ProductsController < ApplicationController
def new
#product = current_user.products.build
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:project).permit(:title, :description, :category_id, :video, :thumbnail, emails_attributes: [:id, :address, :_destroy])
end
end
3) View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.association :category %>
<div id="emails">
<%= f.simple_fields_for :emails do |email| %>
<%= render 'emails_fields', f: email %>
<div class="links">
<%= link_to_add_association 'add email', f, :emails %>
</div>
<%= end %>
</div>
<%= f.button :submit %>
<% end %>
In your _emails_fields partial:
<div class="nested-fields">
<%= f.input :address %>
<%= link_to_remove_association "Remove email", f %>
</div>
Then setup cocoon's gem and javascript and you'll be good.
Reference: https://github.com/nathanvda/cocoon

How to get all jobs for current user and view every job

Missing keys required [:id] for child association show path
I have a job model that belongs to a user. In the user show page, I call all child jobs that belong to the user. Here is my code:
<% current_user.jobs.each do |j| %>
<%= j.id %>
<%= j.job_category %>
<%= link_to 'show', job_path(j.id) %> <br> <br>
<% end %>
But it raises an error anytime I click on show jobs page:
No route matches {:action=>"show", :controller=>"jobs", :id=>nil, :user_id=>"1"} missing required keys: [:id]
Please, how do I rectify this error? the routes show jobs resources stands on its own with path: job_path. it works when I manually plug in the URL.
Here are my routes.rb:
resources :users do
resources :jobs
resources :applications
end
resources :jobs do
collection do
match 'search' => 'jobs#search', via: [:get, :post], as: :search
end
end
and my controller code is:
class JobsController < ApplicationController
def index
#q = Job.ransack(params[:q])
#jobs = #q.result(distinct: true)
end
def frontpage
#q = Job.ransack(params[:q])
if params[:q].present?
redirect_to jobs_path(#q)
end
#jobs = Job.all
end
def search
index
render :index
end
def show
#job = Job.find(params[:id])
end
def edit
end
def update
#joblists.update(joblists_params)
redirect_to joblists_path
end
def create
#user = User.find(params[:user_id])
#user_job = #user.jobs.create(joblists_params)
flash[:notice] = "Job has been successfully Created"
redirect_to user_path(#user)
end
def joblists_params
params.require(:job).permit(:job_category, :job_title, :company_name, :location, :job_description,
:monthly_salary, :deadline, :contact, :longitude, :lattitude, :full_time )
end
end
Look after running rails routes it showing like this
user_job GET /users/:user_id/jobs/:id(.:format) jobs#show
so the link path is
user_job_path
on this link, you need to pass the user_id and job id then it will like this
<%= link_to 'show', user_job_path(#user, j.id) %> # or #user.id
Solved after chat
<% current_user.jobs.each do |j| %>
<%= j.id %>
<%= j.job_category %>
<%= link_to 'show', user_job_path(current_user.id, j.id) %> <br><br>
<% end %>
Add user id
user_job_path(current_user.id, j.id)
Thanks for comment

Rails Strong Params Issue With Nested Models

I am trying to render a new view on an already existing user show page. When trying to submit this view, I get param is missing or the value is empty: user. To be clear this is a skill partial being rendered on the user show page. For some reason it is using the strong params in my User Controller.
The code:
show.html.erb for user
<h4>Create a Skill</h4>
<%= render partial: "skills/form" %>
userscontroller.rb
def show
#user = User.find(params[:id])
#skill = Skill.new
#skills = #user.skills.all
end
private
def user_params
params.require(:user).permit(:username, :password, :avatar_url, :email, :about, :cover_letter, :city, :state)
end
end
SkillsController.rb
class SkillsController < ActionController::Base
def new
user = User.find(params[:user_id])
#skill = user.skills.new
end
def create
user = User.find(params[:user_id])
#skill = user.skills.new(skill_params)
if #skill.save
flash[:message] = "#{#skill.name} skill has been created!"
redirect_to user_path(user)
else
redirect_to new_user_skill_path
end
end
private
def skill_params
params.require(:skill).permit(:name, :level)
end
end
Also, I have Namespaced skills within user. No authentication in place yet.
EDIT: #nickm, here are the contents of skills/_form
<%= simple_form_for(Skill.new, :url => { :action => "create" }) do |f| %>
<%= f.input :name, label: 'Skill Name ' %>
<%= f.input :level, label: "Skill Level ", collection: ["Beginner","Proficient", "Intermediate", "Advanced", "Expert"], include_blank: false, include_hidden: false %>
<%= f.submit %>
<% end %>
The problem is that you aren't passing a user_id through the form. You would have to either add a form input:
<%= f.hidden_field :user_id, some_value %>
Then find the user:
user = User.find(params[:skill][:user_id])
and then make skill_params
def skill_params
params.require(:skill).permit(:name, :level, user_id)
end
Or optionally, set the value of user_id in your controller action. Not sure how you're going to pass that value since you haven't built any authentication yet. If you were using something like devise you could do
current_user.skills.new(skills_params)
...in your create action.

Rails id parameter not persisting

So, essentially, I have two models, Ticket
class Ticket < ActiveRecord::Base
belongs_to :event
end
And Event:
class Event < ActiveRecord::Base
belongs_to :club
has_many :tickets
accepts_nested_attributes_for :tickets
end
They are associated with each other, and I have done the necessary migrations.
In the events/show view, it shows the event and then at the end I have a link to create a ticket, with this event's name passed as an id:
<%= link_to 'Add tickets', new_ticket_path(:id => #event.name) %>
This renders properly in the new ticket page, I have tested it in the new ticket view with <%= params[:id] %> and it comes up correctly.
The tickets_controller's create method is as follows:
def create
#ticket = Ticket.new(ticket_params)
#ticket.event_id = params[:id]
...
end
But when testing back in the events/show view
<% #tickets = Ticket.all %>
<% #tickets.each do |ticket| %>
<p><%= ticket.event_id %>--</p>
<% end %>
All of the event_id's come up empty.
Any suggestions?
The problem is you're passing the :id param to the new method, and then expecting it to persist to the create method
Params are HTTP-based values -- they don't last longer than a single request. You'll have to either use a nested resource, or set the event_id attribute in your new form:
Route
#config/routes.rb
resources :events do
resources :tickets #-> /events/:event_id/tickets/new
end
This will keep the params[:event_id] param consistent (as it's being passed in the URL), allowing you to use it in your create action like so:
def create
#ticket = Ticket.new(ticket_params)
#ticket.event_id = params[:event_id]
...
end
This is the conventional way to do this
Attribute
If you want to just test your code, you should set the event_id param in your new action form:
<%= form_for [#event, #ticket] do |f| %>
<%= f.hidden :event_id, params[:id] %> #-> sets params[:ticket][:event_id]
<% end %>
This will allow you to set the event_id param in your strong_params method in your create action:
def new
#event = Event.find params[:event_id]
#ticket = Ticket.new
end
def create
#ticket = Ticket.new(ticket_params)
#ticket.save
end
private
def ticket_params
params.require(:ticket).permit(:event_id)
end

Rails 3.2.13 undefined method `photos_path' with nested resources with CarrierWave

I was hoping someone can give me some insights on how nested resources and more specifically how to tie it with CarrierWave.
I'm getting an error undefined method 'photos_path' and am a bit stuck on how do go about getting this to work.
I want to be able to have the following urls
Create a new photo
site.com/animal/dog/photos/new
Show photo
site.com/animal/dog/photos/my-dog-in-the-park
Should I be using a nested form? any help is much appreciated.
My Routes
root to: "home#index"
resources :animal do
resources :photos
end
My home view
<%= link_to "Add Dog Photo", new_animal_photo_path(animal_permalink: "dog") %>
My _form partial
<%= form_for [#animal, #photo], :html => {:multipart => true} do |f| %>
<%= f.hidden_field :animal_permalink %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
<%= f.file_field :image %>
</p>
<p>
</p>
<p><%= f.submit %></p>
<% end %>
My Photo model
class Photo < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
before_create :set_permalink
before_update :set_permalink
belongs_to :dog
mount_uploader :image, PhotoUploader
def set_permalink
self.permalink = title.parameterize
end
def to_param
permalink.parameterize
end
end
My Animal model
class Animal < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
has_many :photos
scope :dog, where(name: "Dog")
def to_param
permalink.parameterize
end
end
My PhotoController
class PhotosController < ApplicationController
def show
#photo = Photo.find(params[:id])
end
def new
#animal = Animal.find_by_permalink(params[:id])
#photo = Photo.new
end
def create
#photo = Photo.new(photo_params)
if #photo.save
flash[:notice] = "Successfully created photo."
redirect_to root_url
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(params[:id])
end
def update
#photo = Photo.find(params[:id])
if #photo.update_attributes(photo_params)
flash[:notice] = "Successfully updated photo."
redirect_to root_url
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to root_url
end
private
def photo_params
params.require(:photo).permit(:title, :image, :animal_id)
end
end
Thanks for taking a look. Any help is much appreciated.
I've made a few adjustments to your code.
Notice I've added an "s" to animal
root to: "home#index"
resources :animals do
resources :photos
end
notice I've removed an "s" from animals. I've changed animal_permalink to id because that is the default that would be expected by a nested resource. Also, note that, in your photos controller new method you check for the "id" param, not the animal_permalink param.
new_animal_photo_path(id: "dog")
In your _form. set the value of the animal_permalink
<%= f.hidden_field :animal_permalink, value: #animal.permalink %>
This assumes that Photo will be able to recognize the animal_permalink attribute and that you've defined an animal.permalink method/attribute. I'm not familiar with the permalink approach, so you may have to fiddle around a bit, but this should steer you in the right direction (I would normally set the :dog_id/:animal_id attribute).
Let me know how that goes.

Resources