S3 upload in fewest possible steps - ruby-on-rails

So I setup my app to upload documents to S3 (following Ryan Bates' railscast). Currently, there's a form to upload a file is on the index page for the document resource. On successful upload to S3, that form renders the new actions where the user can rename the file (as it's named locally).
But I'd like to refactor this to eliminate redirecting the user to the '/new' page. I don't want to offer the user a chance to rename the file.
There's got to be a way to upload the file to S3 and then have it post to the create action.
This is what my controller looks like now.
class DocumentsController < ApplicationController
before_filter :authenticate_user!
def index
#page_title = "Docs"
#documents = household.documents.all
#uploader = Document.new.file_upload
#uploader.success_action_redirect = new_document_url
end
def new
#page_title = "New Doc"
#document = Document.new(household_id: household.id, key: params[:key])
end
def create
#doc = Document.new(params[:document])
if #doc.save
flash[:notice] = "Consider it done!"
redirect_to documents_path
else
flash[:notice] = "Oops. Please try again."
render 'new'
end
end
end
How do I simplify this situation and avoid rendering the /new page? Instead of rendering the /new, I want to just post to the create action. I need modify this line:
#uploader.success_action_redirect = new_document_url
but I don't know what it should be.

Perhaps you can create a method in DocumentsController that serves as a REST endpoint. You post from the client via Ajax and then upload to S3 in the endpoint method. This way the client's experience remains unchanged with the upload.
Something like this:
def upload
uploader = Document.new(params_from_ajax_post).file_upload
respond_to do |format|
format.html { render :json => {:message => 'Upload successful.'} }
end
end
In routes.rb:
match 'documents/upload' => 'documents#upload', :via => :post, :as => 'upload'

Related

Rails 4 - controller not being called

I can hit the importers#import_vendor_ledger action but I can't seem to hit the importers#import_chart_of_accounts action via the redirect_based_on(arg) method inuploaders#upload_file. Please help!
NOTE: I left out some code that I didn't think was necessary to see
My code:
routes.rb
resources :uploaders do
collection { post :upload_file }
end
resources :importers do
collection { post :import_vendor_ledger, :import_chart_of_accounts }
end
index.html.haml
#chart_of_accounts
= form_tag upload_file_uploaders_path, multipart: true do
= hidden_field_tag :account, "chart_of_accounts"
= file_field_tag :file
= submit_tag 'Upload Chart of Accounts'
#vendor_ledger
= form_tag upload_file_uploaders_path, multipart: true do
= hidden_field_tag :account, "vendor_ledger"
= file_field_tag :file
= submit_tag 'Upload'
uploaders_controller.rb
class UploadersController < ApplicationController
include Excel
def upload_file
uploaded_io = params[:file]
if uploaded_io.path.downcase.end_with?(xlsx_extension)
save_to_storage(uploaded_io)
flash[:success] = 'File uploaded successfully!'
redirect_based_on_(params) # this is where it should call the action
else
flash[:warning] = 'ERROR: The file you upload MUST be an ".xlsx" excel file!'
redirect_to root_url
end
end
private
def redirect_based_on_(_params)
case _params[:account]
when "vender_ledger"
redirect_to import_vendor_ledger_importers_path and return
when "chart_of_accounts"
redirect_to import_chart_of_accounts_importers_path and return
end
end
end
importers_controller.rb
class ImportersController < ApplicationController
include Excel
def index
end
def show
end
def import_vendor_ledger # I can hit this action
puts "hits vendor ledger import"
end
def import_chart_of_accounts # but I can't hit this action
puts "hits chart of accounts import"
end
EDIT #1: even if I explicitly call redirect_to import_chart_of_accounts_importers_path in uploaders#upload_file it still doesn't hit the importers#import_chart_of_accounts action
EDIT #2: after inspecting more, it seems that importers#import_chart_of_accounts action IS being hit, but none of the functions in the action is being called
Change your route to something like this:
resources :importers do
collection do
get :import_vendor_ledger
get :import_chart_of_accounts
end
end
EDIT: Since you are redirecting to those two paths, I believe, you need those to be GET routes. Since redirect_to issues a 301 request which will be GET request.

Flash messages in rails 4 not showing (within partial, within modal)

The problem in brief: I'm working on a rails 4 app (4.1.8) and I'm trying to get flash[:notice] and flash[:alert] to show up under a form.
Two controllers: landingpage_controller and contacts_controller. The landingpage_controller serves a static landingpage through its show action and the contacts_controller has new and create actions, to store the contacts in a db table.
On the static landingpage, a modal with id="contact-modal" contains a partial with a simple_form_for #contact (see below). Upon submittal of the form, a db-entry is not created if the fields are not all filled out and a db-entry is created if the fields are filled out. However, no flash messages are displayed.
Wanted output:
Ideally the partial would re-load without leaving/closing the modal, with either: a success message and an empty form or a alert message and the form as it was upon submittal. How do I do this?
The controller: app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = Contact.new
render layout: "contact"
end
def create
#contact = Contact.new
respond_to do |format|
if #contact.save
flash[:notice] = "Success"
format.js
else
flash[:alert] = "Error"
format.js
end
end
end
private
def contact_params
params.require(:contact).permit(:email, :structure, :message_content)
end
end
The form: app/views/contacts/_new.html.haml
= simple_form_for #contact, html: { id: "contact-form"} do |c|
= c.input :email
= c.input :structure
= c.input :message_content
= c.button :submit
.messages-container
= if flash[:notice]
%p
= flash[:notice]
= if flash[:alert]
%p
= flash[:alert]
Routes:
resources :contacts, only: [:new, :create]
I'm aware that a partial reload probably involves AJAX. I've read several StackOverflow questions on this but have not been able to figure it out. See here, here and these two blog-posts: jetthoughts, ericlondon.
Your help is very much appreciated
There are several problems in your code:
views, that start with underscore are called partials and are not full actions, but just parts of reusable view code (you don't redirect to them, instead you use render since you usually don't want a full page reload.
1.1 Rename your _new.html.haml to _form.html.haml
2.1 Create a new view new.html.erb(I guess you have that already, otherwise your new action might not work properly) with content = render 'form'
From what I understand you don't want the modal to close, just to render a form after successful submission or if there is an error.
In that case:
1.create a create.js.erb file in your views/contacts folder
create.js.erb
$("#your_modal_id").html("<%= j( render 'form') %>")
2. change your create action
def create
#contact = Contact.new(contact_params)
respond_to do |format|
if #contact.save
flash[:notice] = "Success"
format.js
else
flash[:alert] = "Error"
format.js
end
end
end
to your form add remote: true
WARNING: This will leave your form filled in even if it is successful.
More about this topic see:
http://guides.rubyonrails.org/v4.1.8/working_with_javascript_in_rails.html#form-for
Hope it helps, and I hope I didn't forget anything

call controller with ajax in rails

In ruby on rails project, when I create a reporter successfully, page is redirect to another action from another controller; and when the page is redirect, the page is reloaded. In this project, I have 2 controller:
reporters_controller.rb:
class ReportersController < ApplicationController
layout "reporter"
def new
#reporter = Reporter.new
#gomrokaddresses = Gomrokaddress.find(:all)
end
def create
#reporter = Reporter.new(reporter_params)
if #reporter.save
#redirect_to new_reporter_path
redirect_to new_problem_path(:id => #reporter.id)
else
#existreporter = Reporter.find_by(params[:rep_user_name])
redirect_to new_problem_path(:id => #existreporter.id)
end
end
problems_controller.rb
def new
#reporter = Reporter.find(params[:id])
#problem = #reporter.problems.build
end
def create
#reporter = Reporter.find(params[:id])
#problem = #reporter.problems.build(problem_params)
if #problem.save
redirect_to new_problem_path(:id => #reporter.id)
else
redirect_to new_problem_path(:id => #reporter.id)
end
end
reporter.rb
class Reporter < ActiveRecord::Base
has_many :problems
end
problem.rb
class Problem < ActiveRecord::Base
belongs_to :reporter
end
I create reporter and problem with form_for in view. When I complete form_for in new.html.erb (for reporter) and submit, create action (that exist in reporter_controller) is called, and then if information are true, page is redirect to /problems/new. Because of this redirect_to, the page is reload; I don't want reload the page, just when I create the reporter, the form_for of reporter replace with the form_for of problem. How can I do this?
Try this in your controller
redirect_to new_problem_path(:id => #reporter.id), format: 'js'
Hope this helps!
A controller action renders the corresponding view template by default. Here, the action "problems#new" automatically renders "views/problems/new.html.erb".
In your code, you've redirected to the URL represented by new_problem_path, and a GET request to that URL is routed to the "problems#new" action. Thus, the action is invoked and its template is loaded.
However, there are ways to override this default behavior if you want to call the action without loading the view template.
Also, redirect_to is different from AJAX. For AJAX, you'd use something like
def create
...
respond_to do |format|
if #reporter.save
format.html { redirect_to ... }
format.js
else
format.html { render action: ... }
format.js
end
end
end
and then add the option :remote => true to form_for in the form that you use to create the new reporter.
But I'm not sure if this would accomplish what you're trying to do. Could you please explain your question further?

Recommendations for constructing RESTful resources for avatar selection scenario in rails

We have a requirement where a user needs to select their avatar for their profile. On the edit profile page, the user clicks on a Change Picture link which takes them to another page and gives them with two links to get their photo from facebook or gravatar. There is also a preview of the image shown on this page, as well as a save button. The controller for this page is AvatarsController. I have edit and update actions, as well as custom GET actions for facebook and gravatar, so that the route looks like avatar/facebook, and avatar/gravatar. These actions simply query the respective services and create a new avatar model containing the url for the photo. When the user clicks save, the update action is called and the avatar model is saved with the profile. The page is delivered by the edit template, as by default, when a user is created, an empty avatar is also created.
The Profile model (using mongoid) essentially looks like:
def Profile
embeds_one :avatar
end
and the avatar model looks like:
def Avatar
embedded_in :profile
end
The route looks like:
resource :avatar, only: [:edit, :update] do
member do
get 'facebook'
get 'gravatar'
end
end
The controller looks like:
class AvatarsController < ApplicationController
def facebook
url = AvatarServices.facebook(current_user, params[:code])
respond_to do |format|
unless url
format.json { head :no_content }
else
#avatar = Avatar.new({:url => url, :source => "Facebook"})
#avatar.member_profile = current_user.member_profile
format.html { render :edit }
format.json { render json: #avatar }
end
end
end
def gravatar
respond_to do |format|
url = AvatarServices.gravatar(current_user)
unless url
format.json { head :no_content }
else
#avatar = Avatar.new({:url => url, :source => "Gravatar"})
#avatar.member_profile = current_user.member_profile
format.html { render :edit }
format.json { render json: #avatar }
end
end
end
def edit
#avatar = current_user.member_profile.avatar
end
def update
#avatar = current_user.member_profile.avatar
respond_to do |format|
if #avatar.update_attributes(params[:avatar])
format.html { redirect_to edit_member_profile_path }
format.json { head :no_content }
else
format.html
format.json { render json: #avatar.errors }
end
end
end
end
This works, but being fairly new to rails, I'm wondering if rails experts would have set up the 'facebook' and 'gravatar' resources differently, perhaps in a more RESTful manner?
Well, the subfolder is putting the facebook and gravatar controllers into a common namespace. You could use nested routes,
resource :avatar, only: [:edit, :update] do
resource :facebook
resource :gravatar
end
This will route to a FacebooksController and a GravatarsController.
This is kind of what you were thinking anyway, and you won't need a record id for a facebook or gravatar record.
Could you add your controller code? I'm interested to see how you have your actions setup.
If you want to keep things restful, it might just be a matter of creating a controller subfolder for avatars, and created subsequent controllers for gravatar & facebook. You can do this just using a generator
rails g controller avatars/facebook
rails g controller avatars/gravatar

Rails 3 -Render PDF from view and attach to email

I have been using Wicked_pdf to render a view as a PDF and actionmailer to send emails, but I can't get them to work together. I want to attach a PDF version of a certain view to an email using actionmailer and send it out by clicking a link or a button. I have a link_to command that sends out an email. Here is my controller that gets the email generated:
def sendemail
#user = User.find(params[:id])
Sendpdf.send_report(#user).deliver
redirect_to user_path(#user)
flash[:notice] = 'Email has been sent!'
end
Here is what I have in my actionmailer:
class Sendpdf < ActionMailer::Base
default :from => "myemail#email.com"
def send_report(user)
#user = user
attachment "application/pdf" do |a|
a.body = #Something should go here, maybe WickedPDF.new.something?
a.filename = 'MyPDF'
end
mail(:to => user.email, :subject => "awesome pdf, check it")
end
end
I have seen many questions and answers, most dealing with Prawn. It seems like there should be a simple answer to this. Can anyone help?
UPDATE I'm grateful for a suggestion to use as an alternative option in the answer below. However, I would really like to learn how to render a view as a PDF and attach it to my email. I am open to using something different like Prawn or anything else if I need to.
2 good ways to do this the way you want:
1: Create the pdf in the controller, then send that to the email as a param.
# controller
def sendemail
#user = User.find(params[:id])
pdf = render_to_string :pdf => 'MyPDF'
Sendpdf.send_report(#user, pdf).deliver
redirect_to user_path(#user)
flash[:notice] = 'Email has been sent!'
end
# mailer
def send_report(user, pdf)
#user = user
attachments['MyPDF.pdf'] = pdf
mail(:to => user.email, :subject => "awesome pdf, check it")
end
2: Create the pdf in the mailer directly (a little more involved, but can be called from a model)
def send_report(user)
#user = user
mail(:to => user.email, :subject => "awesome pdf, check it") do |format|
format.text # renders send_report.text.erb for body of email
format.pdf do
attachments['MyPDF.pdf'] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => 'MyPDF',:template => 'reports/show.pdf.erb')
)
end
end
end
There are 2 ways for it.
Either, you want the pdf to be embedded in the email you are sending, so that when the user downloads the pdf from the email, there is no request to the render new pdf action for your respective controller.
I don't know how to do this efficiently because I have never done this before.
Or, you just provide a link to the pdf in your email and when the user clicks on it, now the action for creating the pdf is called, and then the user can download it.
This way, if there is a lot of burden on the server for the downloading of the pdf's, you can redirect these requests somewhere else. In short, there is a huge scope for efficiency.
A sample code for the 2nd method(code provided was written by using PDFkit, so change accordingly):
class PdfsController < ApplicationController
def pdf
respond_to do |format|
format.pdf { render :text => wickedPDF.new( Pdf.find(params[:id]).content ).to_pdf }
end
end
...
end
Replace the Pdf.find(params[:id]).content as per your choice, and the to_pdf method, as per wickedPDF.
Then, you can simply pass the link for the pdf download in your email like this
<%= link_to "Download", pdf_pdf_path(pdf, :format => "pdf") %>
or whatever suits as per wickedPDF.

Resources