Fetch Controller Action Response From Tasks and Models - ruby-on-rails

I would love to call a controller action from within a model. Yes, MVC. Thanks.
So, why would i like to call a controller action and fetch the response?
Because my controller knows how to render the file I would like to cache. Why should I duplicate code to collect all data needed by my view?
Setup:
InvoicesController responds_to :html, :pdf
Invoice uses state_machine (:new -> :open -> paid)
What I "need" within the state transition from :new to :open
generate /invoices/:id.pdf
cache the PDF as Invoice#file for later use in delayed_job or simliar
What interface i would love to use elsewhere?
#invoice.build_pdf
Any suggestions?
Update:
I would like to cache the PDF as model attachement for later use (delayed_job mailing, etc)

It may be better to take the code you're using in your controller to render the PDF data, and create a module or class in your lib directory let's say lib/pdf.rb then use that code in both your controller and your model.
Update: You can cache the PDF itself on the filesystem as a file with a timestamp as it's name or something like that that is uniquely identifiable. Then add a file or invoice attribute to the model to store the location in the database for later use.
Update: You can use the block form of respond_to like so:
respond_to do |format|
format.html
format.pdf { InvoiceBuilder.create(#invoice) }
end
Update: It seems that ActionDispatch::Integration::Session is a class for creating and interface for integration testing. But I think you could exploit it for your purposes.
You should be able to use it like this:
# app/models/invoice.rb
class Invoice < ActiveRecord::Base
def get_body
app = ActionDispatch::Integration::Session.new(Rails.application)
app.get("invoices/#{self.id}.pdf")
app.response.body # this returns the rendered content that the browser will see.
end
end
Another way would be to grab the content by using curl i.e. curl http://localhost:3000/invoices/1.pdf and store the output.

Related

Is it possible to display images from rails active storage inside another application using a URL?

I'm trying to access the User avatar object from a rails API/backend using active storage and display it inside an electron desktop application using a <img> tag. I've been trying various combinations of URL formats in the src attribute of the image tag but always run into a rails routing error.
I've tried using the key, and the attached_sgid fields from rails with the URL formats below but neither approaches work.
http://localhost:3000/rails/active_storage/blobs/--active_storage_blobs.key--
http://localhost:3000/rails/active_storage/blobs/--attached_sgid--
Gives this error:
No route matches [GET] "/rails/active_storage/blobs/BAh7CEkiCGdpZAY6BkVUSSIqZ2lkOi8vanVtcHN0YXJ0LWFwcC9Vc2VyLzE_ZXhwaXJlc19pbgY7AFRJIgxwdXJwb3NlBjsAVEkiD2F0dGFjaGFibGUGOwBUSSIPZXhwaXJlc19hdAY7AFQw--2465471c08be19196738d37c7f02be10c4a88bba"
Is it even possible to display an image from active storage in this way?
I believe you should return the url ready to be used from your backend API instead of building the URL on your electron app.
You can find the correct url from your model using:
url_for(user.avatar)
There are some options to return it. If you don't mind making your model a bit dirty, you can create a method called avatar_url and then return this value with your json.
# new method on model
def avatar_url
Rails.application.routes.url_helpers.url_for(avatar)
end
# on controller controller
def show
user = User.find(params[:id])
render json: user, methods: [:avatar_url]
end
A cleaner option is to create a jbuilder on to build the json for you, and add the url there:
# create new jbuilder file app/view/users/show.json.jbuilder
json.id #user.id
json.avatar_url url_for(#user.avatar) if post.avatar.attached?
# on controller controller
def show
#user = User.find(params[:id])
respond_to do |format|
format.json
end
end

writing raw html to a response object in rails (using ActionDispatcher::Response)

How to write the response from the controller using the ActionDispatch::Response object. There seems to be no api that does http://api.rubyonrails.org/classes/ActionDispatch/Response.html.
The below code works which does not use any view. Is the same can be achived using a response object. The reason being having a necessity to write some binary data to html(which is required for the the current rails app being written)
class HelloController < ApplicationController
def index
render :text => "hello" # want to use ActionDispatch::Response object instead of this
end
end
Have you taken a look at send_data? It may be what you're looking for.

A way to compute and store the short url for a model

Let's say you have simple model called Project and you need to store it's short url for later usage, when is the best way to compute it?
The best solution I have for now is a after_create hook, but that leads to code like
short_url || Rails.application.routes.url_helpers.project_path(project, host: HOSTNAME)
It does not feel right to access the url from the model.
In short, where do you put the code to compute the short_url?
Thank you,
I would add this code in the controller.
class ProjectsController < ApplicationController
def create
#project = Project.new(params[:project])
respond_to do |format|
if #project.save
#project.update_attribute(:short_url, project_url(#project))
(..)
end
end
It feels like this code belongs to the controller as it deals with http/url. Storing in the db sounds ok, but asking for the url is the controller's responsibility.
The line:
#project.update_attribute(:short_url, project_url(#project))
needs to be added below the call to .save (or .create), as only then the project_url helper can be called (the project object got its id already).
Is there a reason why you dont want to save the exact short_url instead? So when you need the url for the project object, you can just check if the short_url is present or not. I believe you can just add a decorator to determine the url of project.
#using Draper
class ProjectDecorator < Draper::Decorator
def effective_url
source.short_url.present? ? source.short_url : project_path(source)
# or source.short_url || project_path(source) if short_url will not be an empty string
# or source.short_url || source
end
end

Loading a page into memory in Rails

My rails app produces XML when I load /reports/generate_report.
On a separate page, I want to read this XML into a variable and save it to the database.
How can I do this? Can I somehow stream the response from the /reports/generate_report.xml URI into a variable? Or is there a better way to do it since the XML is produced by the same web app?
Here is my generate_report action:
class ReportsController < ApplicationController
def generate_report
respond_to do |format|
#products = Product.all
format.xml { render :layout => false }
end
end
end
Here is the action I am trying to write:
class AnotherController < ApplicationController
def archive_current
#output = # get XML output produced by /reports/generate_report
# save #output to the database
respond_to do |format|
format.html # inform the user of success or failure
end
end
end
Solved: My solution (thanks to Mladen Jablanović):
#output = render_to_string(:file => 'reports/generate_report.xml.builder')
I used the following code in a model class to accomplish the same task since render_to_string is (idiotically) a protected method of ActionController::Base:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
#output = av.render(:file => "reports/generate_report.xml.builder")
Perhaps you could extract your XML rendering logic to a separate method within the same controller (probably a private one), which would render the XML to a string using render_to_string, and call it both from generate_report and archive_current actions.
What I typically do in this type of situation is to create a separate module/class/model to generate the report (it could even potentially be right in the Product model). This separate component could be in app/models or it could be in lib. In any case, once you have it extracted you can use it anywhere you need it. The controller can call it directly. You can generate it from the console. You can have a cron job generate it. This is not only more flexible, but it also can help smooth out your request response times if the report becomes slow to generate.
Since you are using a template it's understandable that the controller route is convenient, but even if you have to include some kind of ruby templating system in your auxiliary lib, it's still probably going to be less hassle and more flexible then trying to go through the controller.
#output = Product.all.to_xml
I'm sorry, is you question about Xml or about sessions? I mean is the fact that your action generates Xml material to the question? Or do you just want to save the output of the action for latter use?
You said on a "separate" page - you mean on another request? (like after user approved it?)
Why do you want to save the output? Because it should be saved exactly as rendered? (for example user can get frustrated if he clicked to save one report and you saved another)
Or is this thing expensive to generate?
Or may be, I got it wrong and it's about refactoring?

How do I expose data in a JSON format through a web service using Rails?

Is there an easy way to return data to web service clients in JSON using Rails?
Rails resource gives a RESTful interface for your model. Let's see.
Model
class Contact < ActiveRecord::Base
...
end
Routes
map.resources :contacts
Controller
class ContactsController < ApplicationController
...
def show
#contact = Contact.find(params[:id]
respond_to do |format|
format.html
format.xml {render :xml => #contact}
format.js {render :json => #contact.json}
end
end
...
end
So this gives you an API interfaces without the need to define special methods to get the type of respond required
Eg.
/contacts/1 # Responds with regular html page
/contacts/1.xml # Responds with xml output of Contact.find(1) and its attributes
/contacts/1.js # Responds with json output of Contact.find(1) and its attributes
http://wiki.rubyonrails.org/rails/pages/HowtoGenerateJSON
Rails monkeypatches most things you'd care about to have a #to_json method.
Off the top of my head, you can do it for hashes, arrays, and ActiveRecord objects, which should cover about 95% of the use cases you might want. If you have your own custom objects, it's trivial to write your own to_json method for them, which can just jam data into a hash and then return the jsonized hash.
There is a plugin that does just this,
http://blog.labnotes.org/2007/12/11/json_request-handling-json-request-in-rails-20/
And from what I understand this functionality is already in Rails. But go see that blog post, there are code examples and explanations.
ActiveRecord also provides methods to interact with JSON. To create JSON out of an AR object, just call object.to_json. TO create an AR object out of JSON you should be able to create a new AR object and then call object.from_json.. as far as I understood, but this did not work for me.

Resources