I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end
Related
I have a model of follow_ups and volunteers as:
class FollowUp < ApplicationRecord
belongs_to :volunteer
belongs_to :member
belongs_to :concert
end
class Volunteer < ApplicationRecord
enum type: [:admin, :regular]
has_many :follow_ups, dependent: :delete_all
has_many :members, through: :follow_ups
end
Now I wanted to print follow_ups by all volunteers.
It was working fine when I tried in rails console i.e Volunteer.first.follow_ups
I want to show those value in the form, what I tried is:
Volunteer.all.each do |volunteer|
volunteer.follow_ups.concert_id
end
The has_many relation denotes a one-to-many association. Instead of returning a single object, it returns a collection of follow_ups.
That said, you can't do volunteer.follow_ups.concert_id, because follow_ups is an Active Record collection. Make an iteration instead:
volunteer.follow_ups.each { |follow_up| puts follow_up.concert_id }
The Ruby on Rails documentation has great content about Active Record Associations.
To collect such information you should use:
volunteer.follow_ups.pluck(:concert_id)
Edit:
It's very important to note that using pluck is more efficient than using iterators like map and each due to saving server RAM and request time. Then you can print to rails logger:
volunteer.follow_ups.pluck(:concert_id).each{|ci| Rails.logger.info ci}
Edit2
Referring to your text
I want to show those value in the form
If I understand you, you want to show concert_id of each follow_up in the volunteer form. in this case you should add
accepts_nested_attributes_for :follow_ups in your volunteer.rb
then:
<%= form_for #volunteer do |f| %>
<%= f.fields_for :follow_ups do |form_builder| %>
<%= label_tag "custom_label", "follow up id : #{form_builder.object.id}, concert_id : #{form_builder.object.concert_id}%>
<% end %>
<% end %>
The fields_for helper will iterate through all follow_ups , then you can get the object for each follow_up using object which allow you to deal with object directly and get your concert_id attribute from it.
I want to create an invoice in rails. Invoice can have items and each item will have quantity, tax & price. It's a typical invoice we see everyday.
In order to create an invoice what is the best approach.
What is the common model for invoice and items?
I know Items will be a separate model. But how can we have one view for invoice, which creates both the invoice and items added to it?
What I mean is, Inside a new invoice page, there will be list of the clients, and list of the items , But here i'm not sure how to make the association when i create invoice. Is there any good example that i can follow ?
Please I'd appreciate some Help. Or even just a walk through of the steps i need to follow in order to accomplish that...
Here's my basic ERD
Quite a broad question, here's what I'd do:
#app/models/invoice.rb
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :line_items
has_many :items, through: :line_items
accepts_nested_attributes_for :line_items
end
#app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :item
end
#app/models/item.rb
class Item < ActiveRecord::Base
belongs_to :company
has_many :line_items
has_many :invoices, through: :line_items
end
--
#app/models/user.rb
class User < ActiveRecord::Base
has_many :invoices
end
This will be the base level "invoice" association structure - your clients/users can be built on top of it.
Your routes etc can be as follows:
#config/routes.rb
resources :invoices
#app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.new
#invoice.line_items.build
end
def create
#invoice = current_user.invoices.new invoice_params
#invoice.save
end
end
Then your view will be something like this:
#app/views/invoices/new.html.erb
<%= form_for #invoice do |f| %>
<%= f.fields_for :line_items do |l| %>
<%= f.text_field :quantity %>
<%= f.collection_select :product_id, Product.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
This would create the corresponding #invoice, with which you'll be able to call as follows:
#user.invoices.first
Apart from this, I don't have anywhere enough specific information to help specifically
May I recommend using the payday gem? I have created invoice models in the past applications and I'll tell you what, it can get pretty tricky sometimes depending on the type of application you're building. But the reason I like using this gem besides the convenience factor is that it can also render your invoices as a customizable PDF.
It makes adding items to the invoice a breeze as well, for example from their GitHub page:
invoice = Payday::Invoice.new(:invoice_number => 12)
invoice.line_items << Payday::LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
invoice.line_items << Payday::LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
invoice.line_items << Payday::LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
invoice.render_pdf_to_file("/path/to_file.pdf")
Ok, am still a newbie in ruby on rails trying to learn my way around. I have two models (User model and Comment model). Basically a user has a simple profile with an 'about me' section and a photo's section on the same page. Users must be signed in to comment on other users profiles.
My User Model
class User < ActiveRecord::Base
attr_accessible :email, :name, :username, :gender, :password, :password_confirmation
has_secure_password
has_many :comments
.
.
end
My Comment Model
class Comment < ActiveRecord::Base
belongs_to :user
attr_accessible :content
.
.
end
In my comments table, I have a user_id column that stores the id of the user whose profile has been commented on and a commenter_id column that stores the id of the user commenting on the profile.
Comment Form
<%= form_for([#user, #user.comments.build]) do |f| %>
<%= f.text_area :content, cols: "45", rows: "3", class: "btn-block comment-box" %>
<%= f.submit "Comment", class: "btn" %>
<% end %>
My comments Controller
class CommentsController < ApplicationController
def create
#user = User.find(params[:user_id])
#comment = #user.comments.build(params[:comment])
#comment.commenter_id = current_user.id
if #comment.save
.........
else
.........
end
end
end
This works fine storing both user_id and commenter_id in the database. My problem comes when displaying the user comments on the show page. I want to get the name of the user who commented on a specific profile.
In my user controller
def show
#user = User.find(params[:id])
#comments = #user.comments
end
I want to get the name of the user from the commenter_id but it keeps throwing errors undefined method 'commenter' for #<Comment:0x007f32b8c37430> when I try something like comment.commenter.name. However, comment.user.name works fine but it doesn't return what I want. Am guessing am not getting the associations right.
I need help getting the correct associations in the models so as to get the name from the commenter_id.
My last question, how do I catch errors in the comments form? Its not the usual form_for(#user) where you do like #user.errors.any?.
routes.rb
resources :users do
resources :comments, only: [:create, :destroy]
end
Try something like this in your models
class User < ActiveRecord::Base
has_many :received_comments, :class_name => "Comment", :foreign_key => "user_id"
has_many :given_comments, :class_name => "Comment", :foreign_key => "commenter_id"
end
class Comment < ActiveRecord::Base
belongs_to :user # comment about profile
belongs_to :commenter, :class_name => "User", :foreign_key => "commenter_id"
end
check out: http://guides.rubyonrails.org/association_basics.html
you can probably come up with better naming on the has_many collections, received and given were the best I could do on short notice :)
Note: foreign_key is option in many cases, left it in above - i think it helps with clarity
has_many fk refers to the the column in the many table (other table)
belongs_to fk refers to the column in the many table (this table)
I have a database of skills that relate to each other as prerequisites to each other. In an index of skills, I'd like to be able to search through other skills and add 1 or more as prerequisites. It's important to note that I ONLY want the user to be able to add prerequisites, not remove them, as that's taken care of through an up-down voting system. I'm using JQuery Tokeninput and actually have all of this working except for one thing: I can't figure out how to only add prerequisites, rather than replacing all the prerequisites for a particular skill on submit.
Models:
class Skill < ActiveRecord::Base
attr_accessible :skill_relationship_attributes, :prereq_tokens
has_many :skill_relationships
has_many :prereqs, :through => :skill_relationships
has_many :inverse_skill_relationships, :class_name => 'SkillRelationship', :foreign_key => "prereq_id"
has_many :inverse_prereqs, :through => :inverse_skill_relationships, :source => :skill
attr_reader :prereq_tokens
accepts_nested_attributes_for :skill_relationships, :allow_destroy => true
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
end
class SkillRelationship < ActiveRecord::Base
attr_accessible :skill_id, :prereq_id, :skill_attributes, :prereq_attributes
belongs_to :skill
belongs_to :prereq, :class_name => 'Skill'
end
JQuery:
$('#skill_prereq_tokens').tokenInput('/skills.json',
{ theme:'facebook',
propertyToSearch:'title',
queryParam:'search',
preventDuplicates:'true'
});
View:
<%= simple_form_for skill do |f| %>
<%= f.input :prereq_tokens %>
<%= f.submit %>
<% end %>
I feel a bit silly for not getting this before, but I solved my problem by changing how prereq_tokens became prereq_ids in my Skill model.
I just changed this:
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
to this:
def prereq_tokens=(ids)
self.prereq_ids += ids.split(",")
end
That's it. That little plus sign before the equals sign. I hope this helps anyone else who codes too long without a break!
Here is the parent model:
class TypeWell < ActiveRecord::Base
...
has_many :type_well_phases, :dependent => :destroy
accepts_nested_attributes_for :type_well_phases, :reject_if => lambda { |a| a[:phase_id].blank? }, :allow_destroy => true
...
end
Here is the nested model:
class TypeWellPhase < ActiveRecord::Base
belongs_to :type_well
belongs_to :phase
end
Here is the Phase model:
class Phase < ActiveRecord::Base
...
has_many :type_well_phases
...
end
I add nested records in child table (TypeWellPhases) by copying ALL records from my phases (Phase model) table in the parent model's controller as shown below:
class TypeWellsController < ResourceController
...
def new
#new_heading = "New Type Well - Computed"
#type_well = TypeWell.new
initialize_phase_fields
end
private
def initialize_phase_fields
Phase.order("id").all.each do |p|
type_well_phase = #type_well.type_well_phases.build
type_well_phase.phase_id = p.id
type_well_phase.gw_heat_value = p.gw_heat_value
end
end
...
end
I do this because I want to maintain a specific order by the children fields that are added. The part of the code Phase.order("id") is for that since the phases table has these records in a specific order.
After this I use the simple_form_for and simple_fields_for helpers as shown below in my form partial:
= simple_form_for #type_well do |f|
...
#type_well_phases
= f.simple_fields_for :type_well_phases do |type_well_phase|
= render "type_well_phase_fields", :f => type_well_phase
Everything works as desired; most of the times. However, sometimes the ordering of Child rows in the form gets messed up after it has been saved. The order is important in this application that is why I explicitly do this ordering in the private method in the controller.
I am using the "cocoon" gem for adding removing child records. I am not sure as to why this order gets messed up sometimes.
Sorry for such a long post, but I wanted to provide all the pertinent details up front.
Appreciate any pointers.
Bharat
I'll explain you in a more generic way. Say, you have Product and Order models:
= form_for #product do |f|
...
= f.fields_for :orders do |order_fields|
= order_fields.text_field :name
If you want your orders to be sorted by name then just sort them :)
Instead of:
= f.fields_for :orders do |order_fields|
put:
= f.fields_for :orders, f.object.orders.order(:name) do |order_fields|
As you see, the f variable that is a parameter of the block of form_for has method object. It's your #product, so you can fetch its orders via .orders and then apply needed sorting via .order(:name) (sorry for this little confusion: order/orders).
The key to your solution that you can pass sorted orders as the second parameter for fields_for.
P.S. Your using the simple_form gem doesn't affect my solution. It'll work if you add 'simple_' to helpers. Just wanted my answer to be more helpful for others and not too task-related.
If you are using Rails 2.3.14 or older you have to use:
f.fields_for :orders, f.object.orders.all(:order => :name) do |order_fields|
I use this way:
class League < ActiveRecord::Base
has_many :rounds, -> { sort_by_number }, dependent: :destroy
end
class League::Round < ActiveRecord::Base
belongs_to :league
scope :sort_by_number, -> { order('league_rounds.number ASC') }
end
In the view
= form_for league do |f|
= f.fields_for :rounds do |round_form|
# Here rounds are sorted by sort_by_number
This approach allows the use of any scope defined in the model. This approach allows the creation of several differently sorted associations.