I have a file_field option where I navigate to a csv file to upload a series of users. I currently am able to click on the upload users button without a file being added. I want to ensure that I catch this nil exception but cant seem to work out how to do it. Should I change my controller or change the form by disabling the button somehow
I have the following simple form for uploading a file:
<%= simple_form_for :tenant, :html => {:multipart => true}, :url => users_path do |f| %>
<%= f.file_field :csv, :label => 'CSV File' %>
<%= f.submit 'Upload Users' %>
<% end %>
I have the following view following in the controller:
def upload
if request.post?
if params[:tenant][:csv].blank?
flash[:notice] = "Please provide a csv file to upload."
else
file = params[:tenant][:csv].read
CSV.parse(file, headers: true, header_converters: :symbol).each { |row| User.create(row.to_hash) }
end
respond_to do |format|
format.html { redirect_to users_path }
format.json { head :no_content }
end
else
# Return view
end
end
You could either do client side validation or server side. I'd recommend server side as a starting point. Based on what I see in your controller it looks like you do not have a model for the csv upload. It is better practice to refactor your controller csv code into a model as the logic does not belong in the controller. Once you have a model for the csv upload,
validates_presence_of :file
in the model should do the trick.
If you are not familiar with table less models checkout the railscast #219.
Related
I have a Rails app where I have Article and Like models. I want someone to be able to create a Like record, similar to Facebook, where the database records the new record without redirecting them. The two requirements I have are: create a new Like record without redirecting the user and have a flash or js message that confirms the record was created.
I tried putting this in my view. However, when I do this, it creates two identical records:
<%= form_for #like, :remote=> true do |f| %>
<%= f.hidden_field(:article_id, :value => article.id) %>
<%= f.submit "Like" %>
<% end %>
I also, tried this which resulted in one record creating, but it took me to localhost:3000/likes:
<%= form_for #like do |f| %>
<%= f.hidden_field(:article_id, :value => article.id) %>
<%= f.submit "Like" %>
<% end %>
and then in the Like Controller commenting out the format.html and json:
def create
#like = Like.new(params[:like])
respond_to do |format|
if #like.save
# format.html { redirect_to #like, notice: 'Like was successfully created.' }
# format.json { render json: #like, status: :created, location: #like}
else
format.html { render action: "new" }
format.json { render json: #like.errors, status: :unprocessable_entity }
end
end
end
What's the best way to meet my two requirements? Thank you!
I assume Articles and Likes are related. When you want to 'like' an article you are actually updating that article, right? In other words, you would have to change the redirect within the update action of the articles controller.
P.S. I would use some javascript for submitting a form, for example:
<script type="text/javascript">
$(document).ready(function() {
$('.edit_[here_comes_the_name] input[type=checkbox]').click(function() {
$(this).parent('form').submit();
});
});
</script>
This example submits the input from a particular form (the parent) when clicking on the check_box. Of course you would have to adjust it to fit your needs.
I have a really long form which I would like to break up into about 5 partials. When the user hits 'Next' at the bottom of each partial I want to use AJAX to load the next partial until the last partial submits the entire form into the database. Also, if the user hits 'Previous' I need the fields to be populated with what the user filled in previously.
So far I have this which is not working:
users/new.html.erb
<%= form_for(#user, :html => { :class => "form-horizontal" }, remote: true) do |f| %>
...
<%= f.submit "Next" %>
users_controller.rb
def create
#user = User.new(params[:user])
if #user.save
respond_to do |format|
format.html { flash[:success] = "Welcome to Friends First!"
redirect_to #user }
format.js
end
else
render :new
end
end
create.js.erb
$("#site_content").html("<%= escape_javascript(render('layouts/partial2')) %>");
I would put each of the 5 parts into separate divs (display: none) and only show (display: block) the first. When the user clicks "next", I would show the second, etc. The final submit to the create action can also be done via jquery through
$.ajax(
url: '/users',
type: 'post',
data: $("form").serialize()
)
I hope, that helps.
I am trying to set up an upload image page where the user can optionaly upload
an image url instead. I am using carrierwave
The view:
<%= form_for #rating, :html => {:multipart => true} do |f| %>
<p>
<%= f.file_field :pic_url %>
</p>
<p>
<%= f.label :remote_pic_url_url, 'or image url' %>
<br/>
<%= f.text_field :remote_pic_url_url %>
</p>
<div class="actions">
<%= f.submit 'Upload Picture', :class => 'btn btn-primary' %>
</div>
the model:
class Rating < ActiveRecord::Base
attr_accessible :pic_url, :remote_pic_url_url, :rating
mount_uploader :pic_url , ImageUploader
end
when I try to input just the image url, I get a error msg:
Pic url You are not allowed to upload "" files, allowed types: jpg, jpeg, gif, png
How do I make that field optional. I was under the impression that remote_{columnName}_url is the convention for adding additional url field in carrierwave, and that will take care of that for me..
controller code:
# POST /ratings
# POST /ratings.json
def create
#rating = Rating.new(params[:rating])
respond_to do |format|
if #rating.save
format.html { redirect_to #rating, :notice => 'Rating was successfully created.' }
format.json { render :json => #rating, :status => :created, :location => #rating }
else
format.html { render :action => "new" }
format.json { render :json => #rating.errors, :status => :unprocessable_entity }
end
end
end
See this thread.
The error you're getting (You are not allowed to upload "" files, allowed types: jpg, jpeg, gif, png) is the same one you get with the following:
#rating.remote_pic_url_url = "http://www.google.com"
#rating.save
The problem here is that Carrierwave opens the URL, then calls the resulting file with base_uri.path, which returns /, hence the error. If you were entering a URL which has no extension, then this is the cause.
If not, then I'm not sure why it's not working. I use the same approach in my own project (i.e. setting remote_{columnname}_url and then saving the record) and it works fine. Although I don't normally use the extension whitelist validator, I added one and (in the console at least) it works fine as well with valid URLs (i.e. URLs pointing to images with valid extensions).
Can you try the steps below in the console and see if it saves properly? (insert some valid URL to a JPG/GIF/PNG file):
#rating = Rating.new(remote_pic_url_url: 'http://...')
#rating.save
I'm learning to program and got a form running in my Rails 3 app. Now I'm attempting to add ajax to the form so the page doesn't reload after submitting.
I've followed the numerous tutorials but can't quite seem to figure out how to bring it together. The form adds new Objects to the Profile through the following model:
class Profile < ActiveRecord::Base
has_many :objects
end
class Object < ActiveRecord::Base
belongs_to :profile
end
My form in views/profiles/_object_form.html.erb:
<%= form_for(#object, :remote => true) do |f| %>
<% end %>
Where the form and its created objects are rendered in my views/profiles/_about.html.erb:
<div id="newObjects">
<%= render :partial => 'object_form' %>
</div>
<div id="objectList">
<%= render :partial => 'object', :collection => #profile.objects, :locals => {:object_count => #profile.objects.length) %>
</div>
In my objects_controller.rb I have the following create action:
def create
#object = Object.new(params[:object].merge(:author_id => current_user.id))
respond_to do |format|
if #object.save!
format.html {redirect_to profile_path(#object.profile) }
format.js { render }
else
format.html { redirect_to #profile, :alert => 'Unable to add object' }
end
end
end
In views/objects/create.js.erb:
$('#objectList').append("<%= escape_javascript(render #profile.object)) %>");
So I have a form calling an action in another controller to which I want to add ajax. What happens at the moment is that I need to reload the profile to show the newly created object. What am I doing wrong?
CLARIFICATION: Other than the create action in the ObjectsController, I only reference #object once elsewhere. That's in the ProfilesController's show action:
def show
#profile = Profile.find(params[:id])
#superlative = #profile.superlatives.new`
end
Not sure if this is a full code snippet for your create action, but looks like you are trying to call render on an instance variable that doesn't exist... #profile is never set in the create method in the ObjectController...
Perhaps you meant to type $('#objectList').append("<%= escape_javascript(render #object)) %>");
Also noticed that in your existing code you're making a call to render #profile.object, but the Profile class has a has_many relationship with your Object class, so if that was the right code, then you should type render #profile.objects (plural, not singular).
But I would think you would likely want the code I mentioned above, since you are appending onto the list of objects, not rendering the list again?
Fairly new to Rails 3 and have been Googling every which way to no avail to solve the following problem, with most tutorials stopping short of handling errors.
I have created a Rails 3 project with multiple content types/models, such as Articles, Blogs, etc. Each content type has comments, all stored in a single Comments table as a nested resource and with polymorphic associations. There is only one action for comments, the 'create' action, because there is no need for the show, etc as it belongs to the parent content type and should simply redisplay that page on submit.
Now I have most of this working and comments submit and post just fine, but the last remaining issue is displaying errors when the user doesn't fill out a required field. If the fields aren't filled out, it should return to the parent page and display validation errors like Rails typically does with an MVC.
The create action of my Comments controller looks like this, and this is what I first tried...
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#commentable, :notice => 'Comment was successfully created.') }
else
format.html { redirect_to #commentable }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
end
end
When you fill nothing out and submit the comments form, the page does redirect back to it's appropriate parent, but no flash or nothing is displayed. Now I figured out why, from what I understand, the flash won't persist on a redirect_to, only on a render. Now here's where the trouble lies.
There is only the 'create' action in the comment controller, so I needed to point the render towards 'blogs/show' (NOTE: I know this isn't polymorphic, but once I get this working I'll worry about that then). I tried this in the "else" block of the above code...
else
format.html { render 'blogs/show' }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
Anyway, when I try to submit an invalid comment on a blog, I get an error message saying "Showing [...]/app/views/blogs/show.html.erb where line #1 raised: undefined method `title' for nil:NilClass."
Looking at the URL, I think I know why...instead of directing to /blogs/the-title-of-my-article (I'm using friendly_id), it's going to /blogs/the-title-of-my-article/comments. I figure that extra "comments" is throwing the query off and returning it nil.
So how can I get the page to render without throwing that extra 'comments' on there? Or is there a better way to go about this issue?
Not sure if it matters or helps, but the route.rb for comments / blogs looks like this...
resources :blogs, :only => [:show] do
resources :comments, :only => [:create]
end
I've been plugging away at this over the last few weeks and I think I've finally pulled it off, errors/proper direction on render, filled out fields remain filled in and all. I did consider AJAX, however I would prefer to do it with graceful degradation if at all possible.
In addition, I admit I had to go about this a very hacky-sack way, including pulling in a way to pluralize the parent model to render the appropriate content type's show action, and at this stage I need the code to simply work, not necessarily look pretty doing it.
I KNOW it can be refactored way better, and I hope to do so as I get better with Rails. Or, anyone else who thinks they can improve this is welcomed to have at it. Anyway, here is all my code, just wanted to share back and hope this helps someone in the same scenario.
comments_controller.rb
class CommentsController < ApplicationController
# this include will bring all the Text Helper methods into your Controller
include ActionView::Helpers::TextHelper
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#commentable, :notice => 'Comment was successfully created.') }
else
# Transform class of commentable into pluralized content type
content_type = find_commentable.class.to_s.downcase.pluralize
# Choose appropriate instance variable based on #commentable, rendered page won't work without it
if content_type == 'blogs'
#blog = #commentable
elsif content_type == 'articles'
#article = #commentable
end
format.html { render "#{content_type}/show" }
format.xml { render :xml => #commentable.errors, :status => :unprocessable_entity }
end
end
end
private
# Gets the ID/type of parent model, see Comment#create in controller
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
end
end
articles_controller.rb
class ArticlesController < ApplicationController
def show
#article = Article.where(:status => 1).find_by_cached_slug(params[:id])
#comment = Comment.new
# On another content type like blogs_controller.rb, replace with appropriate instance variable
#content = #article
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #article }
end
end
end
show.html.erb for articles (change appropriate variables for blog or whatever)
<h1><%= #article.title %></h1>
<%= #article.body.html_safe %>
<%= render :partial => 'shared/comments', :locals => { :commentable => #article } %>
shared/_comments.html.erb (I'm leaving out the displaying of posted comments here for simplification, just showing the form to submit them)
<%= form_for([commentable, #comment]) do |f| %>
<h3>Post a new comment</h3>
<%= render :partial => 'shared/errors', :locals => { :content => #comment } %>
<div class="field">
<%= f.label :name, :value => params[:name] %>
<%= f.text_field :name, :class => 'textfield' %>
</div>
<div class="field">
<%= f.label :mail, :value => params[:mail] %>
<%= f.text_field :mail, :class => 'textfield' %>
</div>
<div class="field">
<%= f.text_area :body, :rows => 10, :class => 'textarea full', :value => params[:body] %>
</div>
<%= f.submit :class => 'button blue' %>
<% end %>
shared/_errors.html.erb (I refactored this as a partial to reuse for articles, blogs, comments, etc, but this is just a standard error code)
<% if content.errors.any? %>
<div class="flash error">
<p><strong><%= pluralize(content.errors.count, "error") %> prohibited this page from being saved:</strong></p>
<ul>
<% content.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
I slightly refactored #Shannon answer to make it more dynamic. In my 'find_parent' method I'm grabbing the url path and fetching the controller name. In the 'create' method I'm creating an 'instance_variable_set' which creates a dynamic variable for either Articles (#article) or Blogs (#blog) or what ever it may be.
Hopefully you'll like what I've done? Please let me know if you have any doubts or if something can be improved?
def create
#comment = #commentable.comments.new(params[:comment])
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
content_type = find_parent
instance_variable_set "##{content_type.singularize}".to_sym, #commentable
#comments = #commentable.comments
render "#{content_type}/show"
end
end
def find_parent
resource = request.path.split('/')[1]
return resource.downcase
end
You're getting an error because the blogs/show view likely refers to the #blog object, which isn't present when you render it in the comments controller.
You should go back to using the redirect_to rather than render. It wasn't displaying a flash when you made an invalid comment because you weren't telling it to set a flash if the comment wasn't saved. A flash will persist till the next request.