Whats wrong with this form routing? - ruby-on-rails

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)

Related

rails fields_for parent id not being set on child model

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.

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

Trouble on updating record using an 'has_many :through' Record Association

I am using Ruby on Rails 3.0.7 and I have a problem on an associated model validation for an updating controller action. I am using an "has_many :through" association so that I can handle user-category relationships.
In the User model I have:
has_many :user_category_relationships,
:autosave => true,
:dependent => :destroy
has_many :user_categories,
:through => :user_category_relationships,
:source => :user_category,
:dependent => :destroy
In the _form view file I have:
<% #current_user.user_categories.each do |user_category| %>
<%= check_box_tag :user_category_ids, user_category.id, #user.user_categories.include?(user_category), :name => 'user_relationships[user_category_ids][]' %>
<%= label_tag "user_category_#{user_category.id}", user_category.name %>
<% end %>
In the UserCategoryRelationship model I have:
belongs_to :users
belongs_to :categories
# Validation
validates :name_id,
:presence => true,
:uniqueness => {
:scope => [:created_by_user_id, :category_id]
}
validates :category_id,
:presence => true,
:uniqueness => {
:scope => [:created_by_user_id, :name_id]
}
validates :created_by_user_id,
:presence => true,
:uniqueness => {
:scope => [:category_id, :name_id]
}
In the UsersController I have:
def create
...
# `params[:user_relationships][:user_category_ids]` used below are id values
# related to user-category relationships.
unless params[:user_relationships][:user_category_ids]
# Set default user category if not selected.
#user.user_category_relationships.build(
:category_id => '1',
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
else
params[:user_relationships][:user_category_ids].each { |user_category_id|
#user.user_category_relationships.build(
:category_id => user_category_id,
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
}
end
if #user.save
...
end
end
def update
...
# Note: The following code is equal to that in the controller 'create'
# action. However here is my problem because validation (read after
# for more information about).
unless params[:user_relationships][:user_category_ids]
# Set default user category if not selected.
#user.user_category_relationships.build(
:category_id => '1',
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
else
params[:user_relationships][:user_category_ids].each { |user_category_id|
#user.user_category_relationships.build(
:category_id => user_category_id,
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
}
end
if #user.update_attributes(params[:user])
...
end
end
The above code works except for the update action: when I try to run that I get always the same error, as well.
# Validation errors on updating the 'user_category_relationships' associated model
:"user_category_relationships.category_id" =>["has already been taken"]
:"user_category_relationships.name_id" =>["has already been taken"]
:"user_category_relationships.created_by_user_id" =>["has already been taken"]
What I would like to do, since I am not using the "RoR magical\automatic way" at all (that is, I don't use the collection_singular_ids method - see also this and this), is to make properly work the update controller action so that I can still use the "automatic" creation of user-category relationships (triggered on save and update_attributes(...) methods) but avoiding to generate above validation errors (that is, create valid data in the database table). How can I do? What do you advice about this issue?
P.S.: Where I am in trouble is in the update action, precisely figuring out how to "choose"\"filter" and "coding" records to update, to create and to delete in the database having params[:user_relationships][:user_category_ids] as input data.

Rails XML :include

Here are my models:
class User < ActiveRecord::Base
...
belongs_to :picture, :foreign_key => 'picture_id',
:class_name => 'UploadedFile',
:dependent => :destroy
has_many :enrolled_groups, :through => :interests
...
end
class Group < ActiveRecord::Base
has_many :enrolled_users, :through => :interests,
:source => :user
end
I want to get an XML feed for my Groups with the enrolled users and their picture information.
The following line works fine (just with the enrolled users):
render :xml => #group.to_xml(:include => [:enrolled_users] )
How can I also include the picture info in the feed? I tried a bunch of things but can't figure it out... any idea?
If doing .to_xml(:include => [:enrolled_users, :picture]) doesn't work, then the hacky way would be to add (in User class):
def attributes
super.merge(:picture => picture)
end
You should be able to access nested resources by something like this:
render :xml => #group.to_xml(:include => [{:enrolled_users => :picture}])
I couldn't get it to work with the other proposed solutions. The way I ended up doing it was as follow:
In the controller:
render :xml => #group.to_xml(:include => {:enrolled_users => {:methods => :picture_url}})
In the model:
def picture_url
HOST+picture.public_filename(:avatar)
end

Resources