Why can't I build more than one nested attribute here? - ruby-on-rails

this is my form code:
<%= simple_form_for setup_video(#video) do |f| %>
<% f.fields_for :comment_titles do |t| %>
<%= t.input :title, :label => "Comment Title:" %>
<%= t.button :submit, :value => 'Add', :id => 'add_comment_title' %>
<div class='hint'>Let your listeners know what comments you want by adding a guiding title for them. Pose a question, ask for feedback, or anything else!</div>
<% end %>
<% end %>
I have has_many :comment_titles and accepts_nested_attributes_for :comment_titles, :comments in my model. when I create a new comment_title in the form, the old one is replaced. I want an additional one to be built. How can I do this?
Here are the video controller actions:
def new
#video = Video.new
respond_to do |format|
format.js do
render_to_facebox(:partial => 'add_video')
end
end
end
def create
#video = current_user.videos.new(params[:video])
respond_to do |format|
if #video.save
format.html { redirect_to(#video) }
else
format.html { render :action => "new" }
end
end
end
I think this is actually what is needed:
def update
#video = current_user.videos.find(params[:id])
respond_to do |format|
if #video.update_attributes(params[:video])
format.html { redirect_to(#video) }
format.js
else
format.html { render :action => "edit" }
end
end
end

The edit action here will provide a form which will allow you to edit the existing record as well as its nested attributes. This is why it's replacing the existing object.
If you only want people to add new comment titles then I would recommend building a new object in your edit action like this:
def edit
video = current_user.videos.find(params[:id])
video.comment_titles.build
end
Then this will be available as an additional row in your fields_for call. To only make this show new objects:
<% f.fields_for :comment_titles do |t| %>
<% if t.object.new_record? %>
# stuff goes here
<% end %>
<% end %>
However this restricts people to being able to only add new items in an edit action, which may seen counter-intuitive to some users.

Related

Nested controller Resources, how to do update and destroy?

followed a tutorial to help me create instances within a controller. In other words transactions are created on the envelope controller. Like comments on a blog post.
Everything is working perfectly, but I don't know how to edit a transaction now or destroy one. All I need is to find how to edit an existing thing. Let me show you what I have so far:
in views/envelopes/edit (the form code was copied from where you can create new transactions)
<% #envelope.transactions.each do |transaction|%>
<%= form_for [#envelope, #envelope.transactions.build] do |f| %> <!--??? NEED EDIT INSTEAD OF BUILD ???-->
<%= f.text_field :name, "value" => transaction.name %>
<%= f.text_field :cash, "value" => transaction.cash %>
<%= f.submit "Submit" %>
<% end %>
<%= link_to "Remove", root_path %> <!--??? WANT TO REMOVE TRANSACTION ???-->
<% end %>
in routes.rb
resources :envelopes do
resources :transactions
end
in transaction controller
class TransactionsController < ApplicationController
def create
#envelope = Envelope.find(params[:envelope_id])
#transaction = #envelope.transactions.build(transaction_params)#(params[:transaction])
#transaction.save
#envelope.update_attributes :cash => #envelope.cash - #transaction.cash
redirect_to edit_envelope_path(#envelope)
end
def destroy
# ???
end
def update
# ???
end
def transaction_params
params.require(:transaction).permit(:cash, :name, :envelope_id)
end
end
def update
#transaction = #envelope.transactions.find(params[:id])
if #transaction.update(transaction_params)
redirect to #envelope, notice: 'Transaction was successfully updated'
else
redirect_to #envelope, notice: 'Transaction was not updated'
end
end
def destroy
#transaction.destroy
redirect_to #envelope, notice: 'Text here'
end

Follow and unfollow an article

I am trying to create an app where a user can follow or unfollow an article. To do that, I created three models, Customer, Article and Pin.
These are the relationships:
Customer
has_many articles
has_many pins
Article
has_many pins
belongs_to customer
Pins
belongs_to customer
belongs_to article
I believe a Pin must be nested within an Article. My route.rb look like this:
resources :articles do
resources :pins, :only => [:create, :destroy]
end
end
In article#index I have a form for creating or destroying the relationships:
# To create
<%= form_for [article, current_customer.pins.new] do |f| %>
<%= f.submit "Pin" %>
<% end %>
# To destroy which doesn't work because i guess you can't do the form like that
<%= form_for [article, current_customer.pins.destroy] do |f| %>
<%= f.submit "Pin" %>
<% end %>
Here are the corresponding controller actions:
def create
#article = Article.find(params[:article_id])
#pin = #article.pins.build(params[:pin])
#pin.customer = current_customer
respond_to do |format|
if #pin.save
format.html { redirect_to #pin, notice: 'Pin created' }
else
format.html { redirect_to root_url }
end
end
end
def destroy
#article = Article.find(params[:article_id])
#pin = #article.pins.find(params[:id])
#pin.destroy
respond_to do |format|
format.html { redirect_to root_url }
end
end
Now here my two questions:
How do I create a form that would delete the current relationship?
In my form I only want to show one of the buttons. How can I conditionally display the correct button?
You don't need a form to delete the relationship, links will do fine. I assume you'll be iterating through your articles in the index view -- if so, how about something like this?
<% #articles.each do |article| %>
...
<% if (pin = current_customer.pins.find_by_article(article)) %>
<%= link_to 'Unfollow', articles_pin_path(article, pin), :confirm => "Are you sure you want to unfollow this article?", :method => :delete %>
<% else %>
<%= link_to 'Follow', articles_pins_path(article), :method => :post %>
<% end %>
<% end %>
One caveat about using link_to for creating/destroying records is that if javascript is disabled, they will fall back to using GET rather than POST/DELETE. See the documentation for details.

Differences between Rails' has_one and has_many when it comes to automatic data building

Greetings StackOverflow community!
I have been learning Rails for the past few days and extending the basic blog example in the official Rails guide. I wanted to learned more about one-to-one connections since I'm planning on extending the Devise login system with a custom user data table and the rules of normalization dictate that I should make a different table for the actual auth data (the devise user table) and another for the app-specific user info (my own user table).
But now back to simple rails example. The example basically describes an app that can create blog posts, tag them and accept comments (comment's don't really matter right now). A post has_many tags and a tag belongs_to a post. Pretty straightforward. (same thing can be said about the comments, too, but I'll just stick with the tags for this example)
When a user wishes to create a NEW post, the controller has a call to #post = Post.new which prepares an empty post to be edited and created. It also needs to call post.tags.build to make sure that at least one 'baby' tag which would belong to the post is ready for editing 5.times do #post.tags.build end, for instance would prepare not one but five tags for editing.
In the controller's create method, it's enough to create a new post from params[:post] and #post.save it. It saves the tags automatically without any extra function calls needing to be made.
Here's where I started extending. I just wanted to add another table, called post_data, that would get linked one-to-one to the original posts table. post_data contains a foreign key to the post it belongs to, as well as a belongs_to :post instruction in its model. The post has_one :post_data. It also accepts_nested_attibutes_for :post_data.
Again, in the new method of the post controller, post_data needs to be initialized.#post.build_post_data does just this. The form displays just fine the two different models - one as post[title]...etc and the other as post_data[data_field_name_here].
However, in the create name, the post_data entry needs to be manually saved: #post.create_post_data(params[:post_data]) in order for it to be entered into the db. Otherwise it just doesn't get saved.
Now my question is this - how come the has_many objects of the post, the tags, just need to be prepared in the controller's new method, and then get saved in create automatically, while has_one links also need to be manually saved?
I'm just wandering as to why Rails would work like this.
Thanks in advance for your time and patience! :)
Edit: The code files!
posts_controller
class PostsController < ApplicationController
# GET /posts
# GET /posts.json
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
#post = Post.new
#post.build_post_data
5.times do #post.tags.build end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
# This works now - it creates all the needed resources automatically
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, :notice => 'Post was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, :notice => 'Post was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
end
end
end
posts/_form.html.erb
<%= form_for(#post) do |post_form| %>
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= post_form.label :name %><br />
<%= post_form.text_field :name %>
</div>
<%= post_form.fields_for(:post_data) do |pdf| %>
<div class="field">
<%= pdf.label :herp, "DERP POST DATA" %><br />
<%= pdf.text_field :herp %>
</div>
<% end %>
<div class="field">
<%= post_form.label :title %><br />
<%= post_form.text_field :title %>
</div>
<div class="field">
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</div>
<h2>Tags</h2>
<%= render :partial => 'tags/form',
# send some custom variables to the
# rendered partial's context
:locals => { :form => post_form } %>
<div class="actions">
<%= post_form.submit %>
</div>
<% end %>
and the posts.rb model
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
attr_accessible :post_data_attributes, :name, :title, :content
#let's just assume this makes sense, k?
has_one :post_data
accepts_nested_attributes_for :post_data
# TAGS
has_many :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
# reject if all attributes are blank
:reject_if => lambda { |attrs| attrs.all? { |k, v| v.blank? } }
end
Final edit:
Fixed up all the code, syncing it with my working code! :D
If anybody from the distant future still has this issue and my code doesn't work, send me a personal message and we'll sort it out! :)
I believe this:
<%= fields_for(:post_data) do |pdf| %>
Should be this:
<%= post.fields_for(:post_data) do |pdf| %>
Can you try that?

Update database using 'update_attributes' through 'has_many'

I'm having a problem getting my first app (I'm a total newbie) to save a new associated record. I have two models (users and pictures) with a has_many/belongs_to association. I have set up the userController so that it can create a new picture as below:
def new_picture
#user = User.find(current_user.id)
#picture = #user.pictures.build
end
def create_picture
#user = User.find(params[:id])
#picture = #user.pictures.build(params[:picture])
if #picture.save
flash[:notice] = "Your picture was successfully added."
redirect_to :action => 'show', :id => #user.id
else
render :template => "new_picture"
end
end
and I use
<%= link_to("add picture", :action => 'new_picture', :id => #user.id) if current_user %>
to add a new one. But I'd also like to be able to edit. So I updated the usercontroller with some new code:
def edit_picture
#user = User.find(current_user.id)
#picture = #user.pictures.find(params[:id])
end
# When the user clicks the save button update record
def update_picture
#user = User.find(current_user.id)
#picture = #user.pictures.find(params[:picture])
respond_to do |format|
if #picture.update_attributes(params[:picture])
flash[:notice] = "Your picture was successfully updated."
redirect_to :action => 'show', :id => #user.id
else
render :template => "new_picture"
end
end
end
and added the edit link to show.erb:
<%= link_to("edit picture", :action => 'edit_picture', :id => picture.id) if current_user %>
It loads the edit form fine, with the data all in the right place, but on save all it's doing is giving me the error 'ArgumentError in UsersController#update_picture' with a bunch of Unknown key(s) from my pictures table.
Could somebody explain why? I feel like there is one piece of the jigsaw I haven't quite understood here....
Thanks in advance!
UPDATE: View code is as follows:
<h1>New picture for <%= #user.name %></h1>
<% form_for :picture, #picture, :html => { :multipart => true }, :url => {:action => 'update_picture', :id => #user.id} do |f| %>
Can't seem to see your problem in the view code, however you can do the same thing more elegantly (RESTful) as a nested route. That way you might be able to see the problem more clearly.
config/routes.rb:
resources :users do
member do
resources :pictures
end
end
app/controllers/pictures_controller.rb:
class PicturesController < ApplicationController
before_filter :find_picture, :only => [:edit, :update]
def edit
end
def update
if #picture.update_attributes params[:picture]
flash[:notice] = "Your picture was successfully updated."
redirect_to user_path(current_user)
else
render :edit
end
end
protected
def find_picture
#picture = current_user.pictures.find params[:id]
end
end
app/views/pictures/edit.html.erb:
<%= form_for [current_user, #picture] do |f| %>
<!-- some stuff -->
<% end %>
and to link to your edit form:
<%= link_to_if current_user, 'edit picture',
edit_user_picture_path(:user => current_user, :id => picture) %>
I suggest adding 'accepts_nested_attributes_for :pictures to the user model, and then do
<%= form_for #user do |form| %>
.. user fields
<%= form.fields_for :pictures do |picture_form| %>
.. picture fields
<% end %>
<%= form.submit %>
<% end %>
in the view.
Another option is to create a new controller for the pictures. That may be simpler.

validates_presence_of not working properly...how to debug?

In my Review model, I have the following:
class Review < ActiveRecord::Base
belongs_to :vendor
belongs_to :user
has_many :votes
validates_presence_of :summary
end
I submit a new entry as follows in the URL:
vendors/9/reviews/new
The new.html.erb contains a form as follows:
<%= error_messages_for 'review' %>
<h1>New review for <%= link_to #vendor.name, #vendor%></h1>
<% form_for(#review, :url =>vendor_reviews_path(#vendor.id)) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :summary %><br />
<%= f.text_area :summary, :rows=>'3', :class=>'input_summary' %>
<%= f.hidden_field :vendor_id, :value => #vendor.id %>
</p>
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When I leave the field for :summary blank, I get an error, not a validation message:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.name
Extracted source (around line #3):
1: <%= error_messages_for 'review' %>
2:
3: <h1>New review for <%= link_to #vendor.name, #vendor%></h1>
I don't understand what is happening, it works if :summary is populated
def new
#review = Review.new
#vendor = Vendor.find(params[:vendor_id])
#review = #vendor.reviews.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #review }
end
end
def create
#review = Review.new(params[:review])
##vendor = Vendor.find(params[:vendor_id]) #instantiate the vendor from the URL id -- NOT WOKRING
##review = #vendor.reviews.build #build a review with vendor_id -- NOT working
#review = #current_user.reviews.build params[:review]#build a review with the current_user id
respond_to do |format|
if #review.save
flash[:notice] = 'Review was successfully created.'
format.html { redirect_to review_path(#review) }
format.xml { render :xml => #review, :status => :created, :location => #review }
else
format.html { redirect_to new_review_path(#review) }
format.xml { render :xml => #review.errors, :status => :unprocessable_entity }
end
end
end
My guess is that when it fails it is going to redirect_to new_review_path(#review) and so doesn't know the vendor it. How can I redirect to vendor/:vendor_id/reviews/new instead?
You probably don't have #vendor member variable set - but to fix this, it would be more correct to use not #vendor directly, but through your #review variable instance.
If you are creating new review, you already have #review member variable created, and you simply are populating fields in it - so, you need to set the vendor for #review (unless it's optional)... it would be more correct to use #review.vendor.name instead.
(If vendor is optional, then you obviously must catch all vendor.nil? cases.)
What code do you have in the new and create actions in your ReviewsController?
I suspect that your new Review is failing validation because the summary field is blank and then when the form is redisplayed on validation failure, the #vendor instance variable is nil.
You need to make sure that #vendor is assigned a value for both code paths.
I think you need to render :action => 'new' instead of your redirect_to new_review_path(#review). This will keep your error_messages on the #review object. By redirecting you are losing the old object and creating a new one.
As others has said, you also need to make sure you re-populate the #vender variable in your create method before rendering the view.
PS. I like to use the ardes resources_controller plugin for bog standard controller actions like these, makes life a lot easier for me and it handles nested resources really well.

Resources