Using CombinePDF/Prawn/Paperclip all in one method - ruby-on-rails

So I'm not sure if this is a proper question for StackOverflow but with no one else to turn to I figured I'd try here.
Now, the below code works for it's intended purpose. However, I would consider myself a novice with Rails without the experience to foresee any consequences that might arise in the future as my application scales.
So the idea is that when the user clicks on a 'Generate PDF' button, Prawn will generate the custom PDF, CombinePDF will combine this and PDF's from associated sources, then the final PDF will be saved to the Rails.root directory (only because I don't know how to pass a save location to CombinePDF and have searched everywhere), then Paperclip will attach it to its appropriate model, then the original PDF generated in Rails.root will be deleted to clean up the directory.
Orders show action in Orders Controller
def show
#orders = Order.find(params[:id])
#properties = Property.find(#orders.property.id)
#deeds = Deed.where(property_id: #properties, order_id: #orders).all
#mortgages = Mortgage.where(property_id: #properties, order_id: #orders).all
#attached_assets = AttachedAsset.where(property_id: #properties, order_id: #orders).all
#owners = Owner.where(property_id: #properties, order_id: #orders).all
respond_to do |format|
format.html
format.pdf do
order_pdf = OrderPdf.new(#orders, #properties, #deeds, #mortgages, #attached_assets).render
combine_order_pdf = CombinePDF.new
combine_order_pdf << CombinePDF.parse(order_pdf)
if #deeds.any?
#deeds.each do |deed|
combine_order_pdf << CombinePDF.load(deed.document.path)
end
end
if #mortgages.any?
#mortgages.each do |mtg|
combine_order_pdf << CombinePDF.load(mtg.document.path)
end
end
if #attached_assets.any?
#attached_assets.each do |assets|
combine_order_pdf << CombinePDF.load(assets.asset.path)
end
end
combine_order_pdf.save "order_#{#orders.order_number}.pdf"
paperclip_pdf = File.open("order_#{#orders.order_number}.pdf")
#orders.document = paperclip_pdf
#orders.save
File.delete("order_#{#orders.order_number}.pdf")
redirect_to property_order_path(#properties, #orders)
flash[:success] = "PDF Generated Successfully. Please scroll down."
end
end
end
Generate PDF button in Orders show view
<%= link_to fa_icon("files-o"), order_path(#orders, format: "pdf"), class: "m-xs btn btn-lg btn-primary btn-outline" %>
To be clear: This code does work, but what I'm asking is:
Is all this logic appropriate in a controller function or is there a better place for it?
Will this process bottleneck my application?
Is there a better way to save directly to Paperclip after CombinePDF/Prawn do their thing?
Can I pass a save location to CombinePDF so its not in Rails.root?
Is storing Paperclip attachments in Rails.root/public the only way to have them able to be accessed/displayed on an intranet Rails app?
Is there anything I'm not seeing about this method that may put my application at risk for either performance, security, or stability?
Again, I understand if this isn't an appropriate question(s) but I have no one else to ask so if it is then let me know and I'll take it down. I guess to satisfy the 'Answerable' criteria for this question would be anyone who could tell me the rails way of doing this and why and/or answering the above questions. Thanks in advance for any input!

Is all this logic appropriate in a controller function or is there a better place for it?
Yes, ideally your action should be of 7-8 lines and remaining piece can either become another action or go inside a module which you can place in concerns folder. ex: you can take out your pdf logic and write another method inside concerns folder with file name orders_related.rb
module OrdersRelated
def self.parsing_pdf(orders, properties, deeds, mortgages, attached_assets)
order_pdf = OrderPdf.new(orders, properties, deeds, mortgages, attached_assets).render
.
.
#orders.document = paperclip_pdf
#orders.save
File.delete("order_#{#orders.order_number}.pdf")
redirect_to property_order_path(#properties, #orders)
end
end
Will this process bottleneck my application?
Yes, this kind of processing should alway happen in the background. Won't go into the details of which you should be using as it depends upon your requirements.
Is storing Paperclip attachments in Rails.root/public the only way to have them able to be accessed/displayed on an intranet Rails app?
No, You should be using a storage service like s3 bucket for to keep your files. there are many advantages of doing it which again is out of scope of this question.
Is there anything I'm not seeing about this method that may put my application at risk for either performance, security, or stability?
Yes, your method clearly needs lot of refactoring I can suggest few
Remove .all from all query it is not required
add index to these columns (property, order) in all table
Never save important files to your public directory(use third party storage service)
p.s: I intentionally left two questions as I don't have much experience with CombinePdf.

Related

How to create multiple models in one action in the Right Way?(Ruby on Rails 5)

In fact, this issue keep popping up in my mind when I write the code that there are multiple models in one action.
In create, update action that I found besides using the association build API, otherwise I can't avoid that when one model failed but the others was success and they already exist then I can't redemption or revocation them.
The situation is as follows :
Remove an good from the warehouse when an order is created.
And the order is not actually related to the goods in the warehouse
I provide some simple examples of this issue:
def create
#order = PackageOrder.new(order_params)
if #order.save
stock_record = PackageStockRecord.new(stock_record_params)
if stock_record.save
msg = 'stock_record saving success'
else
$ERROR.error "stock_record: #{stock_record.error_msgs}"
msg = 'stock_record saving failed'
end
redirect_to action: 'index', msgs: msg
else
$ERROR.error "package_order: #{#order.error_msgs}"
flash.now[:error] = 'package_order create failed.'
render 'new'
end
end
From the above example, it can be found that when #order is created or updated successfully, subsequent models can no longer affect or inform it. (except build api
So what I want is to undo the action of previous models and make it be notified when subsequent models was failed in the Right Way. (not by using the build api)
Or is it possible to create a short-lived association?
Is this normal? I mean is this situation inevitable or may be accumulated to cause system errors, or maybe I shouldn't care about it.
Are there relevant documents or procedures that can be learned in this situation?
If you want a model to be affected by changes in another model then that's done through association after all model is just class mapped to the table so you interact with the table in the ruby code.

Rails: form generation based on conditions

I've got a form to build a Document. That form needs adjusting depending on what type of Document a user has chosen. At this point I've got a deferring kind of method in new.html.erb that goes like this:
<%= render 'form_'+#template.label.downcase.parameterize.underscore %>
Which works fine but it's kinda difficult to manage though because when new types of documents are added I need to create actual HTML files and upload them.
Is there a better way to manage this kind of form generation? A view with hundreds of if statements in it feels cumbersome too.
You can push it to document_helper or decorator like :
module DocumentHelper
def form_render
return 'form_#{type}'
end
end

Allows user to edit pages that contain variables [duplicate]

This question already has an answer here:
How to render a string as an erb file?
(1 answer)
Closed 9 years ago.
I have some editable pages that are stored as text in my database. These pages will be called in my view like...
#app/views/static_pages/scheduling_text.html.erb
<%= Page.find_by_name("New Registration").content %>
Page.content is of type 'text' (not string). A portion of the text that contains variables would look like...
You have successfully registered for New Student Orientation on #{<%= #registration.orientation.class_date %>} at...
If course when I call this content in the view, I just get the text, not the model values. How can I make these pages access the model values? I also tried adding #{} around the text without success.
This seems to be a duplicate of Rails: storing erb templates in database
Given that, this should do the trick for you, or at least be close enough to get you started:
<%= sanitize ERB.new(Page.find_by_name("New Registration").content).run %>
Additionally, you can remove the sanitize if content is not user-supplied (this is primarily a security concern).
I've done something exactly like this using HAML processing:
= sanitize Haml::Engine.new(article.content).render
For reference, here's the appropriate ERB documentation: http://ruby-doc.org/stdlib-2.0.0/libdoc/erb/rdoc/ERB.html
OK, after much wailing and gnashing of teeth here is the the solution I cam up with. First I am using mustache. This is much safer than storing erb in the templates and prevents malicious injection of sql into your app. I simply added 'mustache' to my gemfile rather than mustache-rails as it seems to be more up to date. I then created a simple Page model with two attributes: :name and :content. I am using the page model to store the raw mustache code.
Here are the relevant files...
In my controller I call...
#app/controllers/registrations_controller.rb
def create
#registration = Registration.new(params[:registration])
respond_to do |format|
if #registration.save
if #registration.orientation != nil
format.html { render "scheduling_text.html.erb" }
Then my view looks like...
#app/views/registrations/scheduling_text.html.erb
<%= Mustache.render(Page.find_by_name("New Registration").content, {:registration => #registration }).html_safe %>
<%= link_to 'Back', orientations_path %>
...
Then in my page model I have something like...
You have successfully registered for New Student Orientation on {{ registration.orientation.class_date }} at {{ registration.orientation.class_time}}. Please arrive 10 minutes prior to your scheduled Orientation. Remember, you must attend this Orientation session before you may register for classes. ...
Using a page model with scaffolding like this works well because it gives you the new, update, and create actions that will allow users to edit content. Note, they can easily mess up your ruby variables, so thats the downside. Just let your users know to not munk with anything that is between {{}}.
Hope this helps someone else out.

Rails best practices - Controller or model?

I want to use this piece of code to retrieve a user's list of credit cards on file with Stripe to show on his profile (/users/:id)
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
Thing is, I'm not exactly sure where (in terms of Rails best practices) it fits. My first tought is to put it in the show method of the User controller since it's not really business logic and doesn't fit in the model. I've also looked at helper methods but they seem (from my understanding) to be used strictly when toying around with HTML.
Can any of you Rails experts chime in?
Thanks!
Francis
Good question. Whenever you see an instance variable in rails (starting with a #), it usually is a view/controller bit of code.
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
However looking at the tail end of that
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
This might fit better of in a model, where you can reuse that same line, but have the safety of added error handling and predictable behavior. For example
# user.rb
def stripe_customer_cards
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
rescue Stripe::InvalidRequestError
false # You could use this to render some information in your views, without breaking your app.
end
Also note the use of self. This usually implies use of a Rails model, because calling self in the controller actually refers to the controller, rendering it almost worthless, unless you really know what you are doing.
EDIT
To render an error message, simply write a call to redirect or render, with the alert option.
if #stripe_cards = current_user.stripe_customer_cards
# Your being paid, sweet!
else
# Render alert info :(
render 'my_view', alert: 'This is an alert'
redirect_to other_path, alert: 'Another alert'
end
I also like to make it a point to mention that you should not handle errors just because you can. Don't handle errors you don't expect. If you handle errors you don't expect it will
Confuse users
Make bugs in code harder to fix
Exaggerate the time before an error is recognized
I'd recommend adding a virtual attribute in your User model:
# app/models/user.rb
def cards
Stripe::Customer.retrieve(stripe_customer_id).cards.all # note the spelling of `retrieve`
end
Then, you'd be able to access all a users cards in the following manner:
user = User.first
#=> #<User id:1>
user.cards
#=> [Array of all cards]

Rails 3.1 Deny editing other users images

I have a web app which uses Devise for authentication. It is a site which allows the user to upload images so the url would be /images/2. There is a separate image controller.
I have found that a user could edit an image they didn't upload by changing then URL, e.g. /images/4/edit. Is there a way to block them from editing other users images and only allowing them to edit their own?
The images controller, model, etc was created using rails g scaffold if that helps.
There are many different solutions for this, but the simplest is when running edit/update/destroy load the image from the current user instead of from all images:
def edit
#image = current_user.images.find( params[:id] )
end
def update
#image = current_user.images.find( params[:id] )
# do whatever has to be done
end
def destroy
#image = current_user.images.find( params[:id] )
# do whatever has to be done
end
Also, using scaffolds is a really bad practice, you should just write our own code, it's simpler, more productive and will lead you to understand how the framework is supposed to work.
You could also use nested controllers for this, but you would have to research a bit to understand how they work and why they might be a better solution.

Resources