rails fields_for parent id not being set on child model - ruby-on-rails

I have been working from the rails api documents for NestedAttributes and FormHelper and searching stackoverflow.
I have the following code in my new.html.haml view:
=form_for listing, :html => {:id => :listing_form, :multipart => :true} do |f|
=f.fields_for :main_picture, (listing.main_picture || listing.build_main_picture) do |fmp|
=fmp.hidden_field :main, :value => 1
=fmp.file_field :image, :class => :picture_select
And the following code in my controller:
def create
#listing = Listing.new(params[:listing])
#listing.save ? redirect_to(:root) : render('listings/new')
end
Here is my listing.rb:
class Listing < ActiveRecord::Base
has_one :main_picture, :class_name => "Picture", :conditions => {:main => true}
attr_accessible :main_picture_attributes
accepts_nested_attributes_for :main_picture, :allow_destroy => true
end
And my picture.rb:
class Picture < ActiveRecord::Base
belongs_to :listing
validates_presence_of :listing
attr_accessible :listing, :main
end
And I get the following error message when I try and submit my form:
main_picture.listing: can't be blank
I can't work out why the framework is not automatically setting the listing_id field of the main_picture (object Picture) to id value of parent Listing object.
Is there something I am doing wrong?

Do you need the validates_presence_of :listing? I suspect that the child record is getting created before the parent object, and so it doesn't have an ID yet.
Removing that line and adding :dependent => :destroy to your has_one :main_picture would ensure you don't end up with orphan picture records.
Alternatively, rewrite your controller:
p = params[:listing]
#listing = Listing.new(p)
#picture = Picture.new(p.delete(:main_picture).merge({:listing => #listing})
etc.

Related

validation and accepts_nested_attributes_for

i have two models :
class Article < ActiveRecord::Base
belongs_to :league
has_many :photos, :dependent => :destroy
attr_accessible :content, :lead, :title, :title_slug,
:created_at, :updated_at,
:league_id, :photos_attributes
accepts_nested_attributes_for :photos
validates :content, :league, :presence => true
validates :lead , :length => {maximum: 1000}, :presence => true
validates :title ,:length => {maximum: 200}, :presence => true
validates_associated :photos
and
class Photo < ActiveRecord::Base
belongs_to :article
attr_accessible :photo
validates :photo, presence: true
has_attached_file :photo , :styles => { :medium => '440x312#', :small => '209x105!'}
end
My ArticlesController is
...
def new
#article = Article.new
#article.photos.build
end
def create
#article = Article.new(params[:article])
if #article.save
redirect_to([:admin,#article])
else
render 'new'
end
end
...
form view is :
= form_for([:admin,#article] , :html => {:multipart => true}) do |f|
- if #article.errors.any?
= render 'errors'
= f.fields_for :photos do |builder|
= builder.label :photo
= builder.file_field :photo
...
i have some question about it :
1) I dont want to save an article without empty photo but now when i dont choose a file my article saves.
2) When i have some errors on article's fields and render 'new' ,my photo field dissapear , what is the rails way to resolve it.
3) in the future i want to add another model: photo_type and assosciate it with photo. Each article will have two photo fields , each with own type (for example: small , big) . I wonder how to render that fields and what can i do to save article with two photos with different types.
Answer for 1: Use validates_associated :photos. Documentation
Answer for 2: I guess that is an file attachment field. For that, this is generally done by setting up a hidden cache field and by using some callbacks. MountUploader uses the same principle.
Answer for 3: Little skeptical, but I guess something will work along this way:
In your Article model, have two associations with Photo as:
has_one :small_photo, :class_name => "Photo"
has_one :big_photo, :class_name => "Photo"
This will enable you to have two sub-form fields present for both types while opening up the form for Article.
Hope it helps. Do comment if last one can work for you in this way. It looks like the good deal to me :)

Updating object with belongs_to associations and nested_attributes

I've got problems with making update action for one of my data objects. I've got:
class UserProfile < ActiveRecord::Base
belongs_to :address, :dependent => :destroy
belongs_to :post_address, :class_name => 'Address', :dependent => :destroy
accepts_nested_attributes_for :address
accepts_nested_attributes_for :post_address
# validations and stuff
end
class Address < ActiveRecord::Base
# validations and stuff
end
And the problem is with the form and action:
= form_for #up, :url => '/profile/edit', :method => :post do |f|
= f.error_messages
#...
= f.fields_for :address, #up.address do |a|
#...
= f.fields_for :post_address, #up.post_address do |a|
#...
.field.push
= f.submit 'Save', :class=>'ok'
Action:
def edit_account
#user = current_user
if request.post?
#up = #user.user_profile.update_attributes(params[:user_profile])
if #up.save
redirect_to '/profile/data', :notice => 'Zmiana danych przebiegła pomyślnie.'
end
else
#up = #user.user_profile
end
end
The error I get looks like this:
Couldn't find Address with ID=3 for UserProfile with ID=2
And it occurs in the line:
#up = #user.user_profile.update_attributes(params[:user_profile])
I think that AR tries to create another Address when the form is submitted but I'm not certain.
Why do I get this error? What's wrong with my code?
So not sure how that works on new since #up.address is nil. Can you try something like:
=f.fields_for :address, (#up.address.nil? ? Address.new() : #up.address) do |a|
#...
= f.fields_for :post_address, (#up.post_address.nil? Address.new() : #up.post_address) do |a|
#...
That might make a difference?
Solved
I just changed the type of association in UserProfile:
has_one :address,
:class_name => 'Address',
:foreign_key => 'user_profile_id',
:conditions => {:is_post => false},
:dependent => :destroy
has_one :post_address,
:class_name => 'Address',
:foreign_key => 'user_profile_id',
:conditions => {:is_post => true},
:dependent => :destroy,
:validate => false
And slightly adjusted the controller. Thanks for help!

has_many with two has_one's

I have a User model that has_many parents.
I want that user model to have one father and one mother.
So my class Parent belongs_to user
Currently I have
class User < ActiveRecord::Base
has_many :parents
has_one :father, :class_name => 'Parent', :foreign_key => 'user_id', :conditions => {:type => 'male'}
has_one :mother, :class_name => 'Parent', :foreign_key => 'user_id', :conditions => {:type => 'female'}
end
class Parent < ActiveRecord::Base
belongs_to :user
end
The problem is in my controller.
...
def edit
#user = User.find(params[:id])
#user.mother = Parent.new(:type => 'female')
#user.father = Parent.new(:type => 'male')
...
When I go into the edit, it creates and throws the 2 parents into the database without even having changed anything in the form. For example, when I click edit on a user, I go to the edit page. When I look into the database, they're already created.
My form looks like so:
= form_for #user do |f|
= f.fields_for :father do |father_form|
etc...
= f.fields_for :mother do |mother_form|
etc...
I've tried doing something alone the lines of this in my controller:
...
#user.parents.build(:type => 'male')
#user.parents.build(:type => 'female')
...
But the form doesn't show up.
Any help would be greatly appreciated.
Try to use
#user.build_father(:type => 'male')
#user.build_mother(:type => 'female')
instead of
#user.mother = Parent.new(:type => 'female')
#user.father = Parent.new(:type => 'male')
in your action

Whats wrong with this form routing?

I have a users model and a book model. Users can read books (as a reader) which creates an entry in the Readings model:
id | reader_id | book_id
Users also have a list of books that they have read. These are stored in the Red (I use Red because the present and past tense of the word 'read' are the same) model which looks the same as the Reading model above.
Now when a user is reading a book, I would like to display a button which represents finishing the book.
The finish action is in the ReadingsController and looks like this:
def finish
#book = current_user.readings.find(params[:id]).book
current_user.stop_reading!(#book)
current_user.make_red! #book
redirect_to :back
end
As you can probably tell, this takes in the id of a record in the readings table, destroys it and makes a new record in the table for recording books red.
The form helper for the "Finish Reading" button currently looks like this:
<%= form_for :reading, current_user.readings.find_by_book_id(book.id), :url => { :controller => :readings, :action => "finish" }, :method => :delete do |f| %>
<div class="actions"><%= f.submit button_text %></div>
<% end %>
But for some reason this renders a form with the wrong id because "9781440506604" is not the id of a record in the readings table, it's the id of a record in the books table (the ISBN-13 of a book to be precise).
<form accept-charset="UTF-8" action="/readings/9781440506604/finish" method="post">
</form>
What is it I'm doing wrong?
EDIT to add reading.rb
class Reading < ActiveRecord::Base
attr_accessible :book_id
# one person reading a new book may cause feed_item creations in multiple users feeds
has_many :feed_items, :as => :event
has_many :comments, :as => :parent, :dependent => :destroy
scope :from_users_followed_by, lambda { |user| followed_by(user) }
# need to pass the class name here because there is no Reader model
belongs_to :reader, :class_name => "User"
belongs_to :book
validates :reader_id, :presence => true
validates :book_id, :presence => true
def self.followed_by(user)
...
end
end
# and user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :avatar, :remember_me, :avatar_url
has_many :readings, :dependent => :destroy,
:foreign_key => "reader_id"
has_many :reads, :through => :readings, :source => :book
has_many :reds, :foreign_key => "reader_id",
:dependent => :destroy
has_many :red, :through => :reds, :source => :book
def reading? book
self.readings.find_by_book_id(book)
end
def read! book
self.readings.create!(:book_id => book.id)
end
def stop_reading! book
self.readings.find_by_book_id(book).destroy
end
def red? book
self.reds.find_by_book_id(book)
end
def make_red! book
unless red? book
self.reds.create!(:book_id => book.id)
end
end
end
By the way I tried making a user who is reading book 1 and doing user.readings.find_by_book_id(1) in the console and it returns a record from the readings table.
as requested
# routes.rb
resources :readings, :only => [:create, :destroy, :show] do
member do
post :create_comment
delete :finish
end
end
Looks like you have got to_param method in your Reading model
try to call id clearly:
current_user.readings.find_by_book_id(book.id).id
UPD
remove :only => [:create, :destroy, :show] from your routes
use this <%= form_for :reading, current_user.readings.find_by_book_id(book.id), :url => { :controller => :readings, :action => "finish", :id => current_user.readings.find_by_book_id(book.id).id }, :html => {:method => :delete} do |f| %>
I'm not particularly knowledgeable about rails 3 (still using rails 2), but shouldn't you be passing more information to the :url param?
This doesn't seem to mention anything about the ID you want to post to:
:url => { :controller => :readings, :action => "finish" }
Shouldn't it be something closer to this:
:url => { :controller => :readings, :action => "finish", :id => reading_id }
(Assuming reading_id to be substituted for the actual ID)

Application design of controller (Rails 3)

This is hard to explain, but I will do my best:
I am building a system where user's can take courses. Courses are made up of steps, that must be taken in order. In the system there are 6 step types (Download, Presentation, Video, Text, Quiz, and Survey)
The way a user accesses a STEP currently is:
http://example.com/courses/2/course_steps/1
As you can tell course_steps are nested under courses.
Below is the show method in course steps:
def show
render "show_#{#course_step.step.step_type.name.downcase}"
end
As you can tell it basically picks a view to show_[TYPE] (quiz, survey, text..etc)
This works fine for simple steps such as a text, video, or download, but for complicated steps such as a quiz, this model does not work well for the following reasons:
How do I validate a form for a quiz or survey as I would be using a different controller (QuizAttemptsController).
It seems to break the REST principal as a quiz, survey..etc should be treated separately. (I know they are step types, but they can have their own actions and validations)
Step Model
class Step < ActiveRecord::Base
belongs_to :step_type
belongs_to :client
has_one :step_quiz, :dependent => :destroy
has_one :step_survey, :dependent => :destroy
has_one :step_text, :dependent => :destroy
has_one :step_download, :dependent => :destroy
has_one :step_video, :dependent => :destroy
has_one :step_presentation, :dependent => :destroy
has_many :course_steps, :dependent => :destroy
has_many :courses, :through => :course_steps
has_many :patient_course_steps, :dependent => :destroy
attr_accessible :step_type_id, :client_id, :title, :subtitle, :summary
validates :title, :presence=>true
validates :summary, :presence=>true
def getSpecificStepObject()
case self.step_type.name.downcase
when "text"
return StepText.find_by_step_id(self.id)
when "quiz"
return StepQuiz.find_by_step_id(self.id)
when "survey"
return StepSurvey.find_by_step_id(self.id)
when "download"
return StepDownload.find_by_step_id(self.id)
when "video"
return StepVideo.find_by_step_id(self.id)
when "presentation"
return StepPresentation.find_by_step_id(self.id)
end
end
end
Step Quiz Model:
class StepQuiz < ActiveRecord::Base
belongs_to :step, :dependent => :destroy
has_many :step_quiz_questions, :dependent => :destroy
has_many :quiz_attempts, :dependent => :destroy
accepts_nested_attributes_for :step
accepts_nested_attributes_for :step_quiz_questions, :allow_destroy => true
attr_accessible :step_id, :instructions, :step_attributes, :step_quiz_questions_attributes
validates :instructions, :presence=>true
end
CourseStep Model
class CourseStep < ActiveRecord::Base
belongs_to :step
belongs_to :course
validates_uniqueness_of :step_id, :scope => :course_id
def next_step()
Course.find(self.course.id).course_steps.order(:position).where("position >= ?", self.position).limit(1).offset(1).first
end
def previous_step()
Course.find(self.course.id).course_steps.order("position DESC").where("position <= ?", self.position).limit(1).offset(1).first
end
end
How would you suggest fixing this?
What you want to do is implement your Model as a Finite State Machine and continually reload the new or edit action until the desired state is reached, then your controller can display different views depending on state to allow multiple steps to happen.
One way I have solved the problem is by adding a member action of "submit_quiz"to the course_steps controller. I am not sure if I like this, as the code looks kind of ugly. I would appreciate feedback.(Note: I Am using CanCan so #course_step is created automagically in the course_steps_controller)
The things I don't like are:
show_quiz view has a lot of code in it
submit_quiz is in the course_steps_controller
quiz_attempt model has virtual attribute of quiz_questions (for validation purposes)
show_quiz.html.erb
<%= form_for (#quiz_attempt.blank? ? QuizAttempt.new(:started => Time.now.utc, :step_quiz_id => #course_step.step.step_quiz.id) : #quiz_attempt, :url => submit_quiz_course_course_step_path(#course_step.course, #course_step)) do |f| %>
<%= render :partial => 'shared/error_messages', :object => f.object %>
<% #course_step.step.step_quiz.step_quiz_questions.each do |quiz_question| %>
<h3><%= quiz_question.value %></h3>
<% quiz_question.step_quiz_question_choices.each do |quiz_question_choice| %>
<%= radio_button_tag("quiz_attempt[quiz_questions][#{quiz_question.id}]", quiz_question_choice.value, f.object.get_quiz_question_choice(quiz_question.id) == quiz_question_choice.value)%>
<%= quiz_question_choice.value %><br />
<% end %>
<% end %>
<%= f.hidden_field(:step_quiz_id)%>
<%= f.hidden_field(:started)%>
<%= submit_tag("Submit Quiz")%>
<% end %>
course_steps_controller.rb
def show
PatientCourseStep.viewed(current_user.id, params[:course_id], #course_step.step.id )
render "show_#{#course_step.step.step_type.name.downcase}"
end
def submit_quiz
#quiz_attempt = QuizAttempt.new(params[:quiz_attempt])
if !#quiz_attempt.save()
render 'show_quiz'
end
end
quiz_attempt.rb
class QuizAttempt < ActiveRecord::Base
belongs_to :step_quiz
belongs_to :patient
attr_accessor :quiz_questions
attr_accessible :step_quiz_id, :patient_id, :started, :ended, :correct, :incorrect, :quiz_questions
validate :answered_all_questions?
def get_quiz_question_choice(quiz_question_id)
unless self.quiz_questions.blank?
quiz_questions[quiz_question_id.to_s]
end
end
private
def answered_all_questions?
#Making sure they answered all the questions
if self.quiz_questions.blank? or self.quiz_questions.try(:keys).try(:count) != self.step_quiz.step_quiz_questions.count
errors.add_to_base "Not all questions were answered"
end
end
end
def submit_quiz
#quiz_attempt = QuizAttempt.new(params[:quiz_attempt])
if !#quiz_attempt.save()
render 'show_quiz'
end
end

Resources