Here's my problem. If I go to Projects#edit, I am unable to change the course it is assigned to. If I attempt to create a new course for it, or choose from an existing one, I get the following error:
Couldn't find Course with ID=23 for Project with ID=62
app/controllers/projects_controller.rb:49:in `update'
{...
"project"=>{"title"=>"Sup",
"due_date"=>"2012-01-23",
"course_id"=>"27", # THIS IS THE ID OF THE NEW COURSE I WANT
"course_attributes"=>{"name"=>"Calc I", # THIS IS THE OLD ONE, I don't need this. Why is it passing this?
"number"=>"MATH102",
"user_id"=>"3",
"id"=>"23"},
"description"=>"bla bla bla"},
"commit"=>"Update Project",
"user_id"=>"3",
"id"=>"62"}
So I can see that it's trying to pass in the course_attributes, but it's not actually setting the new course_id. I don't understand why course_attributes is being passed, the other form is blank, and the course_attributes being passed, are the attributes of the old course. I want to set the course_id to be the course_id being passed (27 in this case).
ProjectsController
def new
#project = #user.projects.new
#courses = #user.courses # Used to populate the collection_select
#project.build_course # I was informed I need to use this to get the form_for to work
end
def edit
#project = Project.find(params[:id])
#courses = #user.courses # Used to populate the collection_select
end
def update
#project = Project.find(params[:id])
#courses = #user.courses
if #project.update_attributes(params[:project])
flash[:notice] = 'Project was successfully updated.'
redirect_to user_projects_path(#user)
else
render :edit
end
end
Line 49 is the call to update_attributes.
Other Information
project.rb
belongs_to :user
belongs_to :course
attr_accessible :course_id, :course_attributes
accepts_nested_attributes_for :course
course.rb
belongs_to :user
has_many :projects
user.rb
has_many :projects
has_many :courses
So a project has a course_id in the database. I'm currently creating or choosing an existing course on the fly on the Projects#new page. Here's my form for that. I use a JavaScript toggle to alternate between the collection_select and the two text_fields.
projects/new.html.haml
= form_for [#user, #project] do |f|
# This is shown by default
= f.collection_select :course_id, #courses, :id, :name, { prompt: true }
.hidden # This is hidden by default and shown using a toggle
= f.fields_for :course do |builder|
= builder.text_field :name, class: 'large', placeholder: 'Ex: Calculus I'
= builder.label :number, 'Number'
= builder.text_field :number, class: 'new_project_course_number'
= builder.hidden_field :user_id, value: current_user.id
Now, if I am on the new project page, and I attach it to a course by choosing an existing course, it will work correctly. The Project will be created, and the course_id will be set correctly.
If I am on the new project page, and I create a course by using my JavaScript toggle, then filling out the Course Name and Course Number, and click Create, then it will also work. The Course will be created, and the Project will be created with the correct course_id.
Sorry for the lengthy post, but I wanted to provide all information I could. Thanks!
UPDATE 1
routes.rb
resources :users do
resources :projects do
collection do
get 'completed'
match 'course/:course_number' => 'projects#course', as: 'course'
end
end
resources :courses
end
Assuming that your Projects#edit form is similiar to your Projects#new
This is creating the course_attributes in params
.hidden # This is hidden by default and shown using a toggle
= f.fields_for :course do |builder|
= builder.text_field :name, class: 'large', placeholder: 'Ex: Calculus I'
= builder.label :number, 'Number'
= builder.text_field :number, class: 'new_project_course_number'
= builder.hidden_field :user_id, value: current_user.id
That's because if a user has a current course it will create fields for every course.
If you want to be able to build a new course on the fly in edit and new change this line to:
= f.fields_for :course, #project.build_course(:user => #user) do |builder|
This will build a new course regardless of whether you're in edit or new. (Also you can remove #project.build_course in your controller this way.)
You don't list your routes, but assuming that is setup correctly, then the ProjectsController update should be able to do something like:
#course = Course.find(params[:course_id])
Related
I have a model Food that has many food_varients. The FoodVarient is a model that will be set by the admin of the system. For Example the food varients could be Hot,Spicy,Extra Cheese, etc. And a food can have these varients.
Since, Food has_many food_varients, I decided to use nested form to allow the admin to create a new food item and also select food_varients that the food might have from the pre-defined food_varients created before-hand by the admin.
This is what my FoodsController looks like:
class FoodsController < DashboardBaseController
# GET /foods/new
def new
#food = current_department.foods.new
#food.food_varients.build
end
# GET /foods/1/edit
def edit
end
def food_params
params.require(:food).permit(:name, :description,food_varients_attributes[:id,:varient_id])
end
end
I have also accepted the nested attribute in my Food.rb file as follows:
class Food < ApplicationRecord
has_many :food_varients, dependent: :destroy
has_many :varients, through: :food_varients, dependent: :destroy
accepts_nested_attributes_for :food_varients, reject_if: proc { |attributes| attributes['varient_id'] == "0" }
And this is what my form looks like, to add food:
= form_for #food do |f|
.field.form-group
= f.label :name
= f.text_field :name, class: 'form-control', tabindex: 1
.field.form-group
= f.label :description
= f.text_area :description, class: 'form-control', tabindex: 3
........
//**Nested Form Starts from here**
- current_department.varients.each do |varient|
= f.fields_for :food_varients do |g|
= g.label :varient_id, varient.title
= g.check_box :varient_id,{} ,varient.id
I created the nested form by looping around each instance of the Varient model, that was created by the admin, and giving the admin the options to add the food_varients as the new food item is being created.
Problem:
The create is working fine, and the nested attributes are being saved as expected.
But, when i try to edit the food item, the nested form is showing duplicate fields.
For Example: If originally the food was set to have varients (sweet, and sour). Now, the edit page of the food item is showing me 4 fields instead of the two, with two checked sweet and sour fields, and two unchecked. sweet and sour fields.
Is there a different approach that i must be trying? Because ever other example that i see, makes the use of text_fields to save the nested attribute dynamically, while I am looping through the already existing varient instance.
You don't need to use nested attributes at all here. Just use the collection helpers along with the varient_ids= setter:
= form_for #food do |f|
.field.form-group
= f.label :name
= f.text_field :name, class: 'form-control', tabindex: 1
.field.form-group
= f.label :description
= f.text_area :description, class: 'form-control', tabindex: 3
.field.form-group
= f.label :varient_ids
= f.collection_select :varient_ids, Varient.all, :id, :name
class FoodsController < DashboardBaseController
# GET /foods/new
def new
#food = current_department.foods.new
end
# ...
def food_params
params.require(:food).permit(:name, :description, varient_ids: [])
end
end
Rails will automatically add/destroy rows on the join table. The correct spelling is also Variant:
variant (plural variants)
Something that is slightly different from a type or norm.
- https://en.wiktionary.org/wiki/variant#English
If you have the following two models and using the simple_form gem to create a form:
class Poll < ApplicationRecord
has_many :poll_options, dependent: :destroy
accepts_nested_attributes_for :poll_options
end
class PollOption < ApplicationRecord
belongs_to :poll
end
The controller:
class PollsController < ApplicationController
def new
#poll = Poll.new
#poll.poll_options.build
end
def edit
end
private
def poll_params
# params.fetch(:poll, {}).permit(:poll_options_attributes)
# params.require(:poll).permit!
params.require(:poll).permit(:title, poll_options_attributes: [ :id, :destroy, :poll_id, :label ])
end
end
The form:
= simple_form_for(#poll) do |f|
= f.input :title, required: true
= f.simple_fields_for :poll_options do |option_a|
= option_a.input :label
= f.simple_fields_for :poll_options do |option_b|
= option_b.input :label
If I submit this on purpose without a required field (label), the page reloads with 4 options, and I submit again, 6 options, etc. For some reason it keeps adding two more to the form.
Also, editing a poll loads 4 options to begin with instead of the 2 saved in the database (it shows all possible combinations of the options).
Any idea on why this would be happening?
Banging my head against the wall for 2 days. Any help would be greatly appreciated.
Ugh, finally figured it out. Here is the solution...
In the controller action, build it twice:
2.times do
#poll.poll_options.build
end
In the view, only loop once. As apparently you can't have two loops to get two instances of the nested form:
= f.simple_fields_for :poll_options do |options|
= options.input :label
Try this:
private
def poll_params
params.require(:poll).permit(poll_options_attributes: [:id, :destroy, ...other poll option params])
end
UPDATE
def new
#poll = Poll.new
#poll.poll_options.build unless #poll.poll_options.any?
end
UPDATE 2
Change simple_form helper to simple_nested_form (don't forget about js in your application.js)
I've no idea anymore, sorry.
I have a student profile page. There's a form on that page which allows you to create new Note record with for that student. Works well.
I want to add a new field above the "Note" text field labeled "Who?" which allows you to enter additional students, thereby logging notes in bulk:
I'm not quite sure how to structure my form and controller action to achieve this bulk creation of new Note records associated with each student listed.
I'm thinking of taking this approach:
POST to the same action (/notes#create) and detect the presence of the array field student_ids in the params and then do something like this:
params[:student_ids].each do |s|
note_params[:event_attributes][:student_id] = s
note = Note.new(note_params)
note.save
end
But I'd have to modify note_params so that it contains the proper student_id reference on each iteration.
Currently, on the singular form, note_params looks like this:
=> {
"content" => "test",
"event_attributes" => {
"student_id" => "2",
"user_id" => "1",
"is_milestone" => "0",
"happened_at_string" => ""
}
}
Is there a better/cleaner/easier way to do this without having to loop through each id and manually modify the params?
You don't need to modify params that way.
params.fetch(:student_ids, []).each do |student_id|
student = Student.find(student_id)
note = student.notes.new(note_params)
note.save
end
I think you'd be better creating some sort of join table for this functionality.
This way, you can create one note, and then "copy" it by simply duplicating the join records. The downside is that one note may be accessible to multiple people, but that's okay:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :created_notes, class_name: "Note", foreign_key: :user_id
has_and_belongs_to_many :notes
end
#app/models/note.rb
class Note < ActiveRecord::Base
belongs_to :user #-> to know who made it
has_and_belongs_to :users
end
Then you can use your notes#create method as follows:
#app/controllers/notes_controller.rb
class NotesController < ApplicationController
def new
#note = current_user.created_notes.new
#users = User.where.not(id: current_user.id)
end
def create
#note = current_user.created_notes.new note_params
#note.save
end
private
def note_params
params.require(:note).permit(:body, :user_ids)
end
end
Thus, you'll be able to use the collection_select (or similar) functionality to define user_ids for your note:
#app/views/notes/new.html.erb
<%= form_for #note do |f| %>
<%= f.collection_select :user_ids, #users, :id, :name %>
<%= f.submit %>
<% end %>
In my posts controller, I am using the following to see the user ID's of different posts made by students:
def new
#post = current_user.posts.build
end
This is very useful. However, it would be more useful if I can see their names and usernames too. Right now I am making students manually type in their name.
How can you make new posts automatically grab the logged in user's username and name?
To see the user's username and name associated with the #post, you can do:
username = #post.user.username
name = #post.user.name
You should always go and ask the user of #post for the attributes that belong to the user.
You could also use the delegate method/Delegation pattern described more here. API description is here.
class Post < ActiveRecord::Base
belongs_to :user
delegate :username, :name, :to => :user
end
Then you can call: #post.username and that will return the username of the user.
I guess that you are using Devise. So in the _form.html.erbyou can show the username like this...First, let's say that each User has_one :Profile (:username, :first_name, :last_name)
so that
user.rb
has_one :profile
profile.rb
belongs_to :user
In your Post form you could do
_form.html.erb
<div class="field">
<%= f.label :username %> : <%= current_user.profile.username %>
or (if you define a method in your Model that will return your first_name and last_name)
<%= f.label :full_name %> : <%= current_user.profile.first_and_last_name %>
or
<%= f.label :full_name %> : <%= current_user.profile.first_name %> <%= current_user.profile.last_name %>
</div>
A post created with the code you've posted will automatically have the user's name available through the .user associative method:
#post = Post.find x
#post.user.name #-> "name" of associated "user" model
Since you've only posted a new method, and have asked the proceeding question, I'll write some code for you:
Right now I am making students manually type in their name
Why do students have to type their name anyway?
The whole point of ActiveRecord (and relational databases) is to give access to associative data; storing user details (name etc) in the users table, and having it accessible through posts.
This is what you'd do with your posts controller:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
#post = current_user.posts.new
end
def create
#post = current_user.posts.new post_params
#post.save
end
def show
#post = Post.find params[:id]
#username = #post.user.name
end
end
This will automatically set the user_id foreign key for your new post, which should allow you to call the user's name using #post.user.name
--
If you wanted to refactor this, to avoid the law of demeter, you'll want to use the delegate method as Dewyze recommended:
#app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
delegate :name, to: :user, prefix: true #-> post.user_name
end
Dewyze's answer is slightly wrong in that his would yield #post.name - you need the prefix if you wanted to identify the record by its model.
So I'm new to rails and I'm not entirely sure what I'm doing wrong. Everything I've read says that I'm doing this right.
I have a relationships between two models.
class Photo < ActiveRecord::Base
has_many :votes
belongs_to :user
end
And
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation, :remember_me
has_many :votes
has_many :photos
end
Here are my Controller methods
def index
#photos = Photo.order("created_at desc").to_a
end
def create
#photo = Photo.new(params[:photo])
#photo.user_id = current_user.id
if !#photo.save
#error = #photo.errors.full_messages.join('. ')
render view_for_new
return
end
end
I know the relationship works because in my view when I do this: <%= photo.user %> I get a user object back, and when I do <%= photo.user.inspect %> it shows all the expected fields with the correct keys and values.
However I want to access fields such as username, email, etc and display those on the page. How do I do this? I've tried doing <%= photo.user.email %> and some other fields that are available but it doesn't seem to be working
Alright figured this out, or at least partially.
Instead of <%= photo.user.email %> I did <%= photo.user.try(:email) %> and that brought the correct attribute back that I was looking for. It seems the association is done correctly. I don't know why <%= photo.user.email %> doesn't work, everywhere I look on line seems to use that sort of syntax.