I am using the Paperclip gem. A user can submit a location with an image. I do not want the user to duplicate an already submitted location, but they can add an image to it.
I have been trying to figure it out. I will leave the important code below:
location.rb
class Location < ApplicationRecord
has_many :submissions
has_many :users, through: :submissions
# Allows submission objects
accepts_nested_attributes_for :submissions
submission.rb
class Submission < ApplicationRecord
belongs_to :user
belongs_to :location
has_attached_file :image, styles: { large: "600x600>", medium: "300x300>", thumb: "150x150#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
locations_controller.rb
class LocationsController < ApplicationController
# Before actions get routed and ran, find_location will occur
before_action :find_location, only: [:show, :edit, :update, :destroy]
# For the Locations/index.html.erb
def index
#locations = Location.all
end
# Binds submission object to the form in new.html.erb
def new
#locations = Location.new
#locations.submissions.build
end
# For creating a new location in the new.html.erb form
def create
#locations = Location.new(user_params)
# Everything went well. User will be sent to #locations show page
if #locations.save
# Redirects to user submitted locations
redirect_to #locations
else
render 'new'
end
end
# Finds new location user submitted by its unique id
def show
end
# Allowing user to update their submitted location
def edit
end
# Updates users edited submission
def update
if #locations.update(user_params)
# Redirects to user submitted locations
redirect_to #locations
else
render 'edit'
end
end
# Deletes users submission
def destroy
#locations.destroy
# Redirects to user submitted locations
redirect_to #locations
end
private
# Used for finding user submitted location (Prevents DRY)
def find_location
#locations = Location.find(params[:id])
end
# Strong parameters for security - Defines what can be update/created in location model
def user_params
params.require(:location).permit(:city, :state, :submissions_attributes => [:image])
end
end
_form.html.erb
<%= form_for #locations, html: {multipart: true} do |f| %>
.
.
.
<!-- User enters image-->
<%= f.fields_for :submissions do |s| %>
<div class="form-group">
<h3>Upload Image:</h3>
<%= s.file_field :image, class: 'form-control' %>
</div>
<% end %>
<% end %>
show.html.erb
<h3>Image: <%= image_tag #locations.image.url(:medium) %></h3>
I get an error:
"undefined method `image' for.."
It looks like you're trying to do form tags for collections of records, which I don't think will work. Instead, I think you need a structure something like:
<% #locations.each do |location| %>
<%= form_for location, html: {multipart: true} do |f| %>
.
.
.
<!-- User enters image-->
<%= f.fields_for location.submissions.new do |s| %>
<div class="form-group">
<h3>Upload Image:</h3>
<%= s.file_field :image, class: 'form-control' %>
</div>
<% end %>
<% end %>
both the form_for and fields_for need to point to a singular resource.
Side note: Paperclip has been deprecated, and it is recommended that you instead use the Rails internal ActiveStorage.
Related
Im following this tutorial: https://www.devwalks.com/lets-build-instagram-in-rails-part-1/
To create a version of instagram. When I upload an image, add a caption and submit, it will redirect to the index page as expected but the data doesnt seem to have been saved. When I open the rails console and try to get the posts with Posts.first, it returns nil.
Controller:
class PostsController < ApplicationController
def index
end
def new
#post = Post.new
end
def create
#post =Post.create(post_params)
#post.save
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:image, :caption)
end
end
Model:
class Post < ActiveRecord::Base
validates :image, presence: true
has_attached_file :image, styles: { :medium => "640x"}
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
form:
<%= simple_form_for #post do |f| %>
<%= f.input :image %>
<%= f.input :caption %>
<%= f.button :submit %>
<% end %>
routes:
resources :posts
root 'posts#index'
Appreciate any ideas.
Thanks
I see a few problems here:
create will save so you don't need another #post.save.
create returns the new Post object, but you have to check if it has been saved successfully or not (via #post.persisted, or via if #post.save).
From 1 & 2, I believe your post was not saved, due to validation on image presence.
Now why it's happening? I guess your form has no multipart/form-data set that the image file was not submitted at all.
To add that to simple_form (paperclip README) :
<%= simple_form_for #post, html: { multipart: true } do |f| %>
I've been battling this for a while now and I can't figure it out. I have a user model using devise. Users can upload songs, and add youtube videos etc..
I'm trying to let users add/delete songs and videos from the devise edit registrations view.
Videos upload fine, but as songs are a nested resource of playlists, which belongs to user, I think I'm getting muddle up.
Music uploads with the same form on it's corresponding page, but not from the devise registration edit view.
routes:
devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions" }
resources :videos
resources :playlists do
resources :songs
end
Devise registrations controller:
def edit
#song = Song.new
#video = Video.new
end
Form in devise edit registrations:
<div id="user-music-box">
<p class="p-details-title"> Upload Music </p>
<%= simple_form_for [#user.playlist, #song] do |f| %>
<%= f.file_field :audio %>
<%= f.button :submit %>
<% end %>
</div>
<div id="user-video-box">
<p class="p-details-title"> Add videos </p>
<%= simple_form_for #video do |f| %>
<%= f.input :youtubeurl %>
<%= f.button :submit %>
<% end %>
</div>
As I said, videos (Which is a youtube url string) create and save no problem. The exact same form for songs, basically seems to just update the user registration. The song information is shown in the server logs, but no playlist_id is present and nothing gets saved.
Songs controller:
def new
if user_signed_in?
#song = Song.new
if current_user.playlist.songs.count >= 5
redirect_to user_path(current_user)
flash[:danger] = "You can only upload 5 songs."
end
else
redirect_to(root_url)
flash[:danger] = "You must sign in to upload songs"
end
end
def create
#song = current_user.playlist.songs.new song_params
#song.playlist_id = #playlist.id
if #song.save
respond_to do |format|
format.html {redirect_to user_path(current_user)}
format.js
end
else
render 'new'
end
end
Playlist.rb
class Playlist < ActiveRecord::Base
belongs_to :user
has_many :songs, :dependent => :destroy
accepts_nested_attributes_for :songs
end
song.rb
class Song < ActiveRecord::Base
belongs_to :user
belongs_to :playlist
has_attached_file :audio
validates_attachment_presence :audio
validates_attachment_content_type :audio, :content_type => ['audio/mp3','audio/mpeg']
end
Unless you're passing songs/playlists through accepts_nested_attributes_for you shouldn't be using registrations#edit. I'll detail both ways to achieve what you want below:
Nested Attributes
#app/models/user.rb
class User < ActiveRecord::Base
has_many :videos
has_many :playlists
has_many :songs, through: :playlists
accepts_nested_attributes_for :videos
end
#app/models/playlist.rb
class PlayList < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :songs
end
#app/models/song.rb
class Song < ActiveRecord::Base
has_and_belongs_to_many :playlists
end
The importance of this is that to use it properly, you're able to edit the #user object directly, passing the nested attributes through the fields_for helper:
#config/routes.rb
devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions" }
#app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < ApplicationController
before_action :authenticate_user!, only: [:edit, :update]
def edit
#user = current_user
#user.playlists.build.build_song
#user.videos.build
end
def update
#user = current_user.update user_params
end
private
def user_params
params.require(:user).permit(:user, :attributes, videos_attributes: [:youtubeurl], playlists_attributes: [song_ids: [], song_attributes: [:title, :artist, :etc]])
end
end
This will allow you to use:
#app/views/users/registrations/edit.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :videos do |v| %>
<%= v.text_field :youtubeurl %>
<% end %>
<%= f.fields_for :playlists do |p| %>
<%= p.collection_select :song_ids, Song.all, :id, :name %>
<%= p.fields_for :song do |s| %>
<%= f.text_field :title %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This will give you a single form, from which you'll be able to create videos, playlists and songs for the #user.
Separate
The other option is to create the object separately.
There is no technical reason for preferring this way over nested attributes; you'd do it to make sure you have the routes in the correct order etc.
As a note, you need to remember that routes != model structure. You can have any routes you want, so long as they define a good pattern for your models:
# config/routes.rb
authenticated :user do #-> user has to be logged in
resources :videos, :playlists, :songs #-> url.com/videos/new
end
# app/controllers/videos_controller.rb
class VideosController < ApplicationController
def new
#video = current_user.videos.new
end
def create
#video = current_user.videos.new video_params
#video.save
end
private
def video_params
params.require(:video).permit(:youtubeurl)
end
end
# app/views/videos/new.html.erb
<%= form_for #video do |f| %>
<%= f.text_field :youtubeurl %>
<%= f.submit %>
<% end %>
The above will require the duplication of the VideosController for Playlists and Songs
I have a rails app with a Project model and a nested Pictures model. Im using the Paperclip gem to upload images to the pictures model, and nested_form gem to nest the Picture model within the projects. Everything was working perfectly, the images were showing and the code seemed to be working fine, until suddenly after working on another part of the app, I started getting the error mentioned in the question title. Specifically, this line in my index page seems to be the issue: <%= link_to image_tag(project.pictures.first.image.url(:thumb)), project %> I cant seem to figure out the problem as it was working fine before. I even reverted back to a previous commit when it was working, and im still getting the same error. Im totally stumped. Any help would be highly appreciated!
Index:
<div id="pictures">
<% #projects.each do |project| %>
<div class="col-md-4">
<div class="box panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><%= project.title %></h3>
</div>
<div class="image">
<%= link_to image_tag(project.pictures.first.image.url(:thumb)), project %>
</div>
<div class="panel-body">
<p>
<strong>Progress:</strong>
<%= progress_bar 0.6, label: true, alternative: 'info', striped: true %>
</p>
<p>
<strong>Status:</strong>
<%= project.status %>
</p>
<p>
<strong>Phase:</strong>
<%= project.phase %>
</p>
<%= link_to 'Show', project %> |
<%= link_to 'Edit', edit_project_path(project) %> |
<%= link_to 'Destroy', project, method: :delete, data: { confirm: 'Are you sure?' } %>
</div>
</div>
</div>
<% end %>
</div>
Projects model:
class Project < ActiveRecord::Base
has_many :pictures, :dependent => :destroy
has_many :teams, :dependent => :destroy
accepts_nested_attributes_for :pictures, :reject_if => lambda { |a| a[:image].blank? }, allow_destroy: true
accepts_nested_attributes_for :teams, :reject_if => lambda { |a| a[:member].blank? }, allow_destroy: true
end
Pictures model:
class Picture < ActiveRecord::Base
belongs_to :project
has_attached_file :image,
path: ":rails_root/public/system/:attachment/:id/:style/:filename",
url: "/system/:attachment/:id/:style/:filename",
:styles => { :medium => "900x900>", :thumb => "300x300>" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
Projects controller:
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#projects = Project.all
respond_with(#projects)
end
def show
respond_with(#project)
end
def new
#project = Project.new
#project.pictures.build
#project.teams.build
respond_with(#project)
end
def edit
end
def create
#project = Project.new(project_params)
if #project.save
flash[:notice] = "Successfully created project."
redirect_to #project
else
render :action => 'new'
end
end
def update
#project.update(project_params)
respond_with(#project)
end
def destroy
#project.destroy
respond_with(#project)
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:id, :title, :description, :status, :phase, :location, :contractor, :designer, :area, :budget, :project_start, :construction_period, :expected_date, :picture_id, :image, pictures_attributes: [:id, :image, :_destroy], teams_attributes: [:project_id, :user_id, :id, :member, :role, :_destroy])
end
end
Pictures controller:
class PicturesController < ApplicationController
before_action :set_picture, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#pictures = Picture.all
respond_with(#pictures)
end
def show
respond_with(#picture)
end
def new
#picture = Picture.new
respond_with(#picture)
end
def edit
end
def create
#picture = Picture.new(picture_params)
#picture.save
respond_with(#picture)
end
def update
#picture.update(picture_params)
respond_with(#picture)
end
def destroy
#picture.destroy
respond_with(#picture)
end
private
def set_picture
#picture = Picture.find(params[:id])
end
def picture_params
params.require(:picture).permit(:image, :id, :project_id)
end
end
Most certainly, one of your projects has no pictures.
When you call project.pictures.first.image.url(:thumb) for each of your projects, you are getting all of its pictures, then the first one of those, and then the image for that first picture.
If your project has no pictures, then project.pictures is an empty set. When you call .first on an empty set, the result is nil. nil is an object, of class NilClass, and it has no method named image. Ensure that each one of your projects has at least one image and you will not see the error. Alternatively you can create an if statement around your link_to, so that you only evaluate that line if !project.pictures.empty?.
I was hoping someone can give me some insights on how nested resources and more specifically how to tie it with CarrierWave.
I'm getting an error undefined method 'photos_path' and am a bit stuck on how do go about getting this to work.
I want to be able to have the following urls
Create a new photo
site.com/animal/dog/photos/new
Show photo
site.com/animal/dog/photos/my-dog-in-the-park
Should I be using a nested form? any help is much appreciated.
My Routes
root to: "home#index"
resources :animal do
resources :photos
end
My home view
<%= link_to "Add Dog Photo", new_animal_photo_path(animal_permalink: "dog") %>
My _form partial
<%= form_for [#animal, #photo], :html => {:multipart => true} do |f| %>
<%= f.hidden_field :animal_permalink %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
<%= f.file_field :image %>
</p>
<p>
</p>
<p><%= f.submit %></p>
<% end %>
My Photo model
class Photo < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
before_create :set_permalink
before_update :set_permalink
belongs_to :dog
mount_uploader :image, PhotoUploader
def set_permalink
self.permalink = title.parameterize
end
def to_param
permalink.parameterize
end
end
My Animal model
class Animal < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
has_many :photos
scope :dog, where(name: "Dog")
def to_param
permalink.parameterize
end
end
My PhotoController
class PhotosController < ApplicationController
def show
#photo = Photo.find(params[:id])
end
def new
#animal = Animal.find_by_permalink(params[:id])
#photo = Photo.new
end
def create
#photo = Photo.new(photo_params)
if #photo.save
flash[:notice] = "Successfully created photo."
redirect_to root_url
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(photo_params)
flash[:notice] = "Successfully updated photo."
redirect_to root_url
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to root_url
end
private
def photo_params
params.require(:photo).permit(:title, :image, :animal_id)
end
end
Thanks for taking a look. Any help is much appreciated.
I've made a few adjustments to your code.
Notice I've added an "s" to animal
root to: "home#index"
resources :animals do
resources :photos
end
notice I've removed an "s" from animals. I've changed animal_permalink to id because that is the default that would be expected by a nested resource. Also, note that, in your photos controller new method you check for the "id" param, not the animal_permalink param.
new_animal_photo_path(id: "dog")
In your _form. set the value of the animal_permalink
<%= f.hidden_field :animal_permalink, value: #animal.permalink %>
This assumes that Photo will be able to recognize the animal_permalink attribute and that you've defined an animal.permalink method/attribute. I'm not familiar with the permalink approach, so you may have to fiddle around a bit, but this should steer you in the right direction (I would normally set the :dog_id/:animal_id attribute).
Let me know how that goes.
I have Users, Users have many Clients and Contacts. Clients also have many Contacts (a Contact belongs to both Users and Clients).
In my client view, I want to create a new Client and on the same form allow the user to create the first Contact for that client. Initially, I thought using nested attributes would do the trick but I'm running into some issues. When I go to save #client in clients_controller#create, I can't save because user_id can't be blank and client_id can't be blank for the Contact. Here's what I have so far:
clients controller index (where the new client form is located):
def index
#clients = current_user.clients
#client = Client.new
#contact = #client.contacts.build
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clients }
end
end
and the create method:
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
and the form:
= form_for(#client) do |f|
= f.fields_for(:contacts) do |contact|
but when I go to save it requires a client_id and user_id...but I can't really set those using the nested attributes. how can I accomplish this? is there a better way of doing this? here's my params:
{"name"=>"asdf", "contacts_attributes"=>{"0"=>{"name"=>"asdf", "email"=>"asdf#gmail.com"}}}
I just tried adding the missing values directly into the contacts_attributes but since #client hasn't been saved yet, I can't assign the client.id to contact:
params[:client][:contacts_attributes]["0"].merge!(:user_id => current_user.id)
params[:client][:contacts_attributes]["0"].merge!(:client_id => #client.id)
even when user_id is set...it still says user is missing.
Did you add accepts_nested_attributes_for to your model? You need something like this:
class Client < ActiveRecord::Base
has_many :contacts
accepts_nested_attributes_for :contacts
end
Also, you should be using build in your create action:
#client = current_user.clients.build(params[:client])
Here's my setup that worked for your example:
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :clients_attributes
has_many :clients
accepts_nested_attributes_for :clients
end
app/models/client.rb:
class Client < ActiveRecord::Base
attr_accessible :company, :contacts_attributes
belongs_to :user
has_many :contacts
accepts_nested_attributes_for :contacts
end
app/models/contact.rb:
class Contact < ActiveRecord::Base
attr_accessible :email
belongs_to :client
end
app/controllers/users_controller.rb:
class UsersController < ApplicationController
# ...
def new
#user = User.new
#client = #user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The actual form:
<% # app/views/users/new.erb %>
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.fields_for :clients do |client_form| %>
<h5>Client</h5>
<%= client_form.label :company, "Company name" %>
<%= client_form.text_field :company %>
<div class="field">
<h5>Contact</h5>
<%= client_form.fields_for :contacts do |contact_form| %>
<%= contact_form.label :email %>
<%= contact_form.text_field :email %>
<% end %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The form looks like this:
Here's how params sent by forms look like:
{
"utf8"=>"✓",
"authenticity_token" => "bG6Lv62ekvK7OS86Hg/RMQe9S0sUw0iB4PCiYnsnsE8=",
"user" => {
"name" => "My new user",
"clients_attributes" => {
"0" => {
"company" => "Client's Company Name LLC",
"contacts_attributes" => {
"0" => {
"email" => "emailbox#client.com"
}
}
}
}
},
"commit" => "Create User"
}
Update
To enable adding more companies/contacts afterwards, change the UsersController#edit action to this:
class UsersController < ApplicationController
# ...
def edit
#client = current_user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The following validations may be causing this problem since the parent id has not assigned to a child object at the time the validations are run for it. The workaround to this is to either remove these validations or set them to run on update only. This way you lose out on these validations for the create method but it works.
# client.rb
validates :user, presence: true
# contact.rb
validates :client, presence: true