I am building a Rails 3 app with a job board, where workers can submit bids for jobs. I have the following associations:
Job:
has_many :bids
has_many :workers, :through => :bid
Bid:
belongs_to :job
belongs_to :worker
Worker:
has_many :bids
has_many :jobs, :through => :bid
I have a form on the Show Job Page in which workers can submit bids. In the controllers, I have the following:
Job Controller:
def show
#bid = current_worker.bids.build
end
Bid Controller:
def create
#bid = current_worker.bids.build(params[:bid])
#bid.save
end
With the code above, a submitted bid will not save correctly because it is missing the job_id:
1) What is the correct way to save the bid with BOTH the job_id and worker_id?
2) (I am having trouble passing the job_id from the Job Controller show method to the Bid Controller create method) - is it secure to pass the job_id in a sessions variable?
For reference, the bid form looks like:
<%= form_for(#bid) do |f| %>
<%= f.label :min_price, "Minimum Price" %>
<%= f.text_field :min_price %>
<%= f.label :fee %>
<%= f.text_field :fee %>
<%= f.label :comments %>
<%= f.text_area :comments, placeholder: "Comments..." %>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
Code depends on what safety you want, I suppose you want to have to protected jobs which current_worker can't make bids to, so you need that does not seems to depend on bid, instead in job.
As you are first creating a bid you can pass job_id, in the form or as part of the route.
If you want to deny a worker to bid to any job you could do something like this:
Bids Controller:
def create
job = Job.find(params[:job_id])
if current_worker.can_bid? job
#bid = current_worker.bids.build params[:bid]
else
# handle unauthorised bidding
In worker model, this is just an example:
def can_bid?(job)
# Implement code here
# example:
# job.public? or invited_to?(job)
end
# example of invited_to?(job)
def invited_to?(job)
job.invitees.include? self
end
I am not sure if this answers your question.
I think you could use this to pass job id in route:
Routes
resources :jobs do
resources :bids
end
View
= form_for #job, #bid ...
As in first time you don't have #job, you can use:
= form_for :job, #bid
I'm a rails newbie but I think instead of saying
<%= form_for(#bid) do |f| %>
try
<%= form_for(#job, #bid) do |f| %>
then in your bid controller new action do something like this
def new
#job = Job.find(params[:job_id])
#bid = #job.bids.build
end
and then in your routes you should nest the bid under the job resources like
resources :jobs do
resources :bids
end
that should do it I hope, like I said I'm a newbie and may be wrong on this.
Just check your model Bid. Its belogs to two models. So You have to make it as polymorphic table. Re Structure your models as follows
Bid:
belongs_to :bidable, polymorphic: true
Job:
has_many :workers, :through => :bid
has_many :bids, :as => :bidable
Worker:
has_many :bids, :as => :bidable
has_many :jobs, :through => :bid
Related
any help would be most appreciated, I am rather new to Rails.
I have two models a Shopping List and a Product. I'd like to save/update multiple products to a shopping list at a time.
The suggested changes are not updating the models. I've been googling and is "attr_accessor" or find_or_create_by the answer(s)?
Attempt 1 - Existing code
Error
> unknown attribute 'products_attributes' for Product.
Request
Parameters:
{"_method"=>"patch",
"authenticity_token"=>"3BgTQth38d5ykd3EHiuV1hkUqBZaTmedaJai3p9AR1N2bPlHraVANaxxe5lQYaVcWNoydA3Hb3ooMZxx15YnOQ==",
"list"=>
{"products_attributes"=>
{"0"=>{"title"=>"ten", "id"=>"12"},
"1"=>{"title"=>"two", "id"=>"13"},
"2"=>{"title"=>"three", "id"=>"14"},
"3"=>{"title"=>"four", "id"=>"15"},
"4"=>{"title"=>"five", "id"=>"16"},
"5"=>{"title"=>""},
"6"=>{"title"=>""},
"7"=>{"title"=>""},
"8"=>{"title"=>""},
"9"=>{"title"=>""},
"10"=>{"title"=>""}}},
"commit"=>"Save Products",
"id"=>"7"}
Attempt 2 - no errors the page reloads and none of the expected fields are updated. In earnest, I am Googling around and copying and pasting code snippets in the vain hope of unlocking the right combo.
Added to Products mode
class Product < ApplicationRecord
attr_accessor :products_attributes
belongs_to :list, optional: true
end
<%= content_tag(:h1, 'Add Products To This List') %>
<%= form_for(#list) do |f| %>
<%= f.fields_for :products do |pf| %>
<%= pf.text_field :title %><br>
<% end %>
<p>
<%= submit_tag "Save Products" %>
</p>
<% end %>
<%= link_to "Back To List", lists_path %>
list controller
def update
#render plain: params[:list].inspect
#list = List.find(params[:id])
if #list.products.update(params.require(:list).permit(:id, products_attributes: [:id, :title]))
redirect_to list_path(#list)
else
render 'show'
end
list model
class List < ApplicationRecord
has_many :products
accepts_nested_attributes_for :products
end
original do nothing - product model
class Product < ApplicationRecord
belongs_to :list, optional: true
end
If you just want a user to be able to select products and place them on a list you want a many to many association:
class List < ApplicationRecord
has_many :list_items
has_many :products, through: :list_products
end
class ListItem < ApplicationRecord
belongs_to :list
belongs_to :product
end
class Product < ApplicationRecord
has_many :list_items
has_many :lists, through: :list_products
end
This avoids creating vast numbers of duplicates on the products table and is known as normalization.
You can then select existing products by simply using a select:
<%= form_for(#list) do |f| %>
<%= f.label :product_ids %>
<%= f.collection_select(:product_ids, Product.all, :name, :id) %>
# ...
<% end %>
Note that this has nothing to with nested routes or nested attributes. Its just a select that uses the product_ids setter that's created by the association. This form will still submit to /lists or /lists/:id
You can whitelist an array of ids by:
def list_params
params.require(:list)
.permit(:foo, :bar, product_ids: [])
end
To add create/update/delete a bunch of nested records in one form you can use accepts_nested_attributes_for together with fields_for:
class List < ApplicationRecord
has_many :list_items
has_many :products, through: :list_products
accepts_nested_attributes_for :products
end
<%= form_for(#list) do |f| %>
<%= form.fields_for :products do |pf| %>
<%= pf.label :title %><br>
<%= pf.text_field :title %>
<% end %>
# ...
<% end %>
Of course fields_for won't show anything if you don't seed the association with records. That's where that loop that you completely misplaced comes in.
class ListsController < ApplicationController
# ...
def new
#list = List.new
5.times { #list.products.new } # seeds the form
end
def edit
#list = List.find(params[:id])
5.times { #list.products.new } # seeds the form
end
# ...
def update
#list = List.find(params[:id])
if #list.update(list_params)
redirect_to #list
else
render :new
end
end
private
def list_params
params.require(:list)
.permit(
:foo, :bar,
product_ids: [],
products_attrbutes: [ :title ]
)
end
end
Required reading:
Rails Guides: Nested forms
ActiveRecord::NestedAttributes
fields_for
I'm trying to do a nested form for a has_many :through association using Simple Form, and I can't figure out how to get around this error: ArgumentError in Variants#edit -- Association cannot be used in forms not associated with an object.
Here's what I'm trying to accomplish. I have a "Product Variant" model (called Variant). Each variant can have many parts (Part model) through a "Parts List Item" (PartsListItem) join model. Each variant should be able to have parts assigned to it in different quantities.
For instance, a guitar strap might have a part called "Backing Fabric" that has a quantity of 1. Meaning that the Guitar Strap variant needs 1 of the "Backing Fabric" part to be assembled. But the same variant might also have another part such as "Rivet" that has a quantity of 4. (As in 4 rivets are required to make this product variant.) After using the Variant form to add all the parts in various quantities to the variant, I'd like to show all of the parts with quantities on the variants#show page.
Here is the relevant code from my models:
class Variant < ApplicationRecord
has_many :parts_list_items, dependent: :destroy
has_many :parts, through: :parts_list_items, dependent: :nullify
accepts_nested_attributes_for :parts
end
class PartsListItem < ApplicationRecord
belongs_to :variant
belongs_to :part
end
class Part < ApplicationRecord
has_many :parts_list_items, dependent: :destroy
has_many :variants, through: :parts_list_items, dependent: :nullify
end
And my VariantsController:
class VariantsController < ApplicationController
def update
respond_to do |format|
if #variant.update(variant_params)
format.html { redirect_to #variant, notice: 'Variant was successfully updated.' }
else
format.html { render :edit }
end
end
end
private
def variant_params
params.require(:variant).permit(:account_id, :product_id, :sku,
:choice_ids => [], :part_ids => [])
end
end
And my form (views/variants/_edit_form.html.erb):
<%= simple_form_for #variant do |f| %>
<%= f.simple_fields_for :parts_list_items do |item| %>
<%= item.input_field :quantity %>
<%= item.association :parts %>
<% end %>
<% end %>
Note that this works just fine:
<%= simple_form_for #variant do |f| %>
<%= f.association :parts, as: :check_boxes %>
<% end %>
So, it works to associate parts directly to the variant through the PartsListItem join model. The trouble begins when I start trying to add the quantity for each associated part.
What am I doing wrong with this nested form? Is there a problem with my controllers or associations?
Do I need to create an additional model called PartsList that has_many :parts_list_items with additional associations? That seems like an extra step and that there should be a way to put the :quantity on the PartsListItem model.
I think you need to change parts to part
<%= simple_form_for #variant do |f| %>
<%= f.simple_fields_for :parts_list_items do |item| %>
<%= item.input_field :quantity %>
<%= item.association :parts %> <!-- HERE -->
<% end %>
<% end %>
I have an enrollment form where a user can enroll to some sort of event.
However, I want to give the posibility for teams to enroll also and I was thinking about a wizard like form.
Basically create 5 records at a time.
The problem is, I'll have a new enrollment creation on each step, so I thought the wicked gem would not do it for this scenario.
Can you give me a few guidelines on how should I approach this?
Maybe just render new after creation if a i.e. team attr is sent from the form?
Maybe use self join?
That's off the top of my head but I know there has to be a clever way to do this.
I'm not sure how your models are structured, but if you have something like:
class Attendee
has_many :enrolments
has_many :events, through: :enrolments
end
class Enrolment
has_many :attendees
belongs_to :event
end
class Event
has_many :enrolments
has_many :attendees, through: :enrolments
accepts_nested_attributes_for :enrolments
end
Then you can do something like:
# controllers/enrolments_controller.rb
class EnrolmentController < ApplicationController
def new
#event = Event.find(params[:event_id])
pax = params[:persons].to_i
pax.times do
#event.enrolments.build
end
end
def create
#event = Event.find(params[:event_id])
#event.enrolments.build(enrolment_params)
#event.save
end
protected
def enrolment_params
# require specific parameters here
params.require(:event).permit(:attendee_attributes => [])
end
end
# views/enrolments/new.html.erb
<%= form_for #event, url: event_enrolments_path(#event) do |f| %>
<%= f.hidden_field :event_id %>
<%= f.fields_for :enrolments do |af| %>
<%= af.select :attendee_id, Attendee.all.collect {|p| [ p.name, p.id ] } %>
<% end %>
<%= f.submit %>
<% end %>
# routes.rb
resources :events do
resources :enrolments
end
That's off the top of my head, but the general idea is that you build the nested fields by running event.enrolments.build based on the number of people passed in the params.
This uses fields_for and accepts_nested_attributes_for. This also makes it really convenient to reuse existing forms by passing in the form context in the partial:
<%= f.fields_for :enrolments do |af| %>
<%= render "enrolments/form", f: af %>
<% end %>
I have a model Job. A job require many skills. But when I'm trying to save my job it fails. I'm not sure I understand what I'm doing wrong.
My models:
class Skill < ActiveRecord::Base
has_many :job_skills
has_many :jobs, through: :job_skills
end
class JobSkill < ActiveRecord::Base
belongs_to :skill
belongs_to :job
end
class Job < ActiveRecord::Base
has_many :job_skills, :inverse_of => :job
has_many :skills, through: :job_skills
accepts_nested_attributes_for :job_skills
end
My view:
<%= form_for #job do |f| %>
<div class="row">
<div class="col-md-8">
<h4>General informations</h4>
<br />
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, :autofocus => true, class:'form-control' %>
</div><br />
<%= f.fields_for :job_skills do |s| %>
<%= s.text_field :id %>
<% end %>
</div>
</div>
<div class="submit" style="position:relative;">
<%= f.submit "Save", class: 'button button-small' %>
</div>
<% end %>
My controller:
class JobsController < ApplicationController
before_filter :authenticate_user!, :has_company?, :except => [:index, :show]
def create
#job = Job.new(job_params)
#job.company_id = current_user.company_id
#job.user_id = current_user.id
if #job.save
flash[:notice] = "This job offer has been saved."
return redirect_to job_path(#job)
else
flash[:error] = #job.errors.full_messages.to_sentence
render action: :new
end
end
def new
if current_user.company.present?
#job = Job.new(email:current_user.email)
#job.job_skill.build
else
flash[:error] = "You need to create a company before posting a job"
return redirect_to new_company_path()
end
end
private
def job_params
params.require(:job).permit(:status, :title, :description, :remote ,:job_type, :visa_sponsor, :email, :salary_max, :salary_min, :country, :state, :city, job_skill_attributes: [:id])
end
end
So, I'm not sure what I'm doing wrong, when I'm trying to save I get the following error:
#job = Job.new(job_params)
ActiveRecord::RecordNotFound Exception:
Couldn't find Skill with ID=4 for Job with ID= nil
Your pieces of code are a bit confusing for me.
It seems, that you want to create a job and define, what skills are needed for this job.
Why do you need nested attributes?
Normally, you
either edit a list of all the skills, that are probably needed for a job and then assign the propper skills to that job, than you have a has_and_belongs_to_many relationship and can use form helpers for collections. In this case, you don't need a model JobSkill (but a table jobs_skills to store the relationship and is handles transparently by Rails)
or add random skills to a job, then your job has_may :skills and every skill belongs to exactly one job. Here you can use nested attributes. And then you need a way to add nested skill instances i.e. with cocoon. Again, you don't need a model JobSkill.
Which one is your usecase, so I can explain it in more detail.
I am building a job board where workers can bid on jobs and bosses can accept a single bid for each job they have, creating an "assignment". On the job show page, already constructed bids can be accepted by a boss. Among other things, I have the following associations:
Job:
has_many :bids
has_many :assignments, :through => :bids
Bid:
has_one :assignment
belongs_to :job
Assignment:
belongs_to :bid
has_one :job, :through => :bid
In the Job Show Page: (form for a boss to accept a bid)
<%= form_for :assignments, :url => '/assignments' do |f| %>
<%= f.submit "Accept Bid", class: "btn btn-mini" %>
<% end %>
In the Job Controller:
Show Action:
#job.bids.each { |bid| bid.build_assignment }
In the Assignment Controller:
Create Action:
#bid = Bid.find(params[:bid_id])
#assignment = #bid.build_assignment(params[:assignment])
#assignment.save
As you can see, I am trying to build an assignment through association with a bid. Questions:
1) When I click "accept bid" (form above), rails displays an error indicating the bid cannot be found (in the assignment controller create action). Why?
2) I also tried doing: <%= f.hidden_field :bid_id %> in the form and changing the Assignment create action to: #assignment = Assignment.new(params[:assignment]), but now the assignment object won't save because apparently there is no bid_id.