Update database using 'update_attributes' through 'has_many' - ruby-on-rails

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.

Related

Rails 4 - "Create" action in controller when it's a nested route

I have a User model that has_many Photos (another model). In the routes.rb file it's photos is a nested resource of user. So, I have a form_for that uploads to the controller #create action. I'm having trouble saving the photo into the database under the user. How can I do this? It says it saves correctly, because it redirects, but then it doesn't show that it has a photo (I also used the console to check not just my view). What am I doing wrong?
class PhotosController < ApplicationController
def new
#photo = Photo.new(:user_id => params[:user_id])
end
def create
byebug
#photo = Photo.new(params[:user_id])
if #photo.save
flash[:notice] = "Successfully added photo."
redirect_to new_user_photo_path
else
render :action => 'new'
end
end
end
EDIT----------
Adding in the form_for that gets us to the #create action. Hope this helps :) I've been reading more about this, and I'm still not sure what to do to fix.
<%= form_for #photo, :url => user_photos_path(current_user.id, #photo), :html => {:multipart => true} do |f| %>
<div class="form-group">
<%= f.file_field :image %>
</div>
<%= f.submit 'Upload', class: 'btn btn-success' %>
or
<%= link_to 'Cancel', users_path %>
<% end %>
class PhotosController < ApplicationController
def new
#user = User.find(params[:user_id])
#photo = Photo.new(:user_id => params[:user_id])
#photos = #user.photos
end
def create
#photo = User.find(params[:user_id]).photos.new(photo_params)
if #photo.save
flash[:notice] = "Successfully added photo."
redirect_to new_user_photo_path
else
render :action => 'new'
end
end
# GET /photos
def index
redirect_to new_user_photo_path
end
# GET /photos/1
def show
#photo = Photo.find(params[:id])
end
def edit
#photos = Photo.all
#photo = Photo.new
end
def destroy
#photo = User.find(params[:user_id]).photos.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully deleted photo"
redirect_to new_user_photo_path
end
def photo_params
params.require(:photo).permit!
end
end

Why is the Photo Id not being passed to create action?

I'm implementing likes for photos, and the id isn't being passed. I don't know why. Could someone please explain why? I feel it would help me understand rails better.
I get this error, after clicking like: "Couldn't find Photo without an ID"
On the photo show page I have this:
<%= form_for(#photolike, :url => {:controller => :photolikes, :action => 'create'}) do |f| %>
<%= f.hidden_field :photo_id, :value => #photo.id %>
<%= f.submit "like", class: "btn postbtn right" %>
<% end %>
this is the controller for photos
def show
if user_signed_in?
#comment = current_user.sent_photocoments.new(params[:photo_comment])
end
#photo = Photo.find(params[:id])
#photolike = Photolike.new
end
And in the photolikes controller, I have this:
def create
#photo = Photo.find(params[:id])
#photolike = Photolike.new(:photo_id => #photo.id, :user_id => current_user.id)
#photolike.addlike
#photolike.save
redirect_to #photo
end
def create
#photo = Photo.find(params[:photolike][:photo_id])
#photolike = Photolike.new(:photo_id => #photo.id, :user_id => current_user.id)
#photolike.addlike
#photolike.save
redirect_to #photo
end
Your submit is submitting the photo_id labeled as photo_id (in your hidden field). So your controller needs to retrieve it via params[:photo_id] rather than params[:id].

undefined method for gallery id

I was following the Railscast tutorial for CarrierWave (#253). However immediately noticed that the creator had already made the gallery model/controller. This was never shown in the video or explained in a different tutorial. I have been creating the gallery and have just about everything working except for this page /photos/new?gallery_id=3 which is suppose to show me a field to upload a photo.
I can go to /galleries and it will list all Galleries created. I can then click on one of the galleries and be taken to /galleries/3 which will present links to Add, Remove, and View Galleries.
The error I am receiving is "undefined method `key?' for nil:NilClass". NoMethodError in PhotosController#new is title of page.
Can someone take a look and point me in the right direction? Thanks.
photos_controller:
class PhotosController < ApplicationController
def new
#photo = Photo.new(:gallery_id => params[:gallery_id])
end
def create
#photo = Photo.new(params[:photo])
if #photo.save
flash[:notice] = "Successfully created photos."
redirect_to #photo.gallery
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(params[:id])
end
def update
#photo = Photo.find(params[:id])
if #photo.update_attributes(paramas[:photo])
flash[:notice] = "Successfully updated photo."
redirect_to #photo.gallery
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to #photo.gallery
end
end
_form.html in views/photos
<%= form_for #photo, :html => {:multipart => true} do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :gallery_id %>
<p>
<%= f.label :name %><br/>
<%= f.text_field :name %>
</p>
<p>
<%= f.file_field :image %>
</p>
<p><%= f.submit %></p>
<% end %>
new.html
<% title "New Photo " %>
<%= render 'form' %>
<p><%= link_to "Back to Gallery", #photo.gallery %></p>
routes.rb
Dating::Application.routes.draw do
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
get 'logout' => 'sessions#destroy'
get 'edit' => 'users#edit'
get "/profile/:id" => "users#show"
resources :users
resources :sessions
resources :password_resets
resources :galleries
resources :photos
root to: 'users#new'
root to: 'galleries#index'

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.

Why can't I build more than one nested attribute here?

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.

Resources