Pre-building view cache in Rails - ruby-on-rails

Is there a way to pre-build a page cache without calling the actual page via a http request?
I looked at solutions like this and this, but these don't generate the cache.
I have a relatively complicated view, and want to cache the entire thing. I want to pre-build this cached version in the application so when a user actually hits it, it will already be there.
Thanks

We had a need to do something similar from a rake task -- we had a partial that would need to display a very long list of entities (~700) which were somewhat context specific and which, due to a series of database structure issues and custom sorting criteria, would easily take > 25 seconds to render the first time before going into cache> This would often time out because our HTTP servers were set to terminate HTTP requests with no response after 30 seconds, and pre-caching this custom list was a solution.
What you need to do is create an instance of ActiveController::Base, or of one of your controllers if you need helper methods or other entities, then pass its lookup_context reference to a new instance of ActionView.Renderer.
In our rake task, we did the following
namespace :rake_for_time_consuming_nonsense do
task :pre_cache_long_list do
PreCacher.pre_fetch_partials
end
end
class PreCacher
def self.pre_fetch_partials
the_controller = ActionController::Base.new
# Set any instance variables required by your partial in the controller,
# they will be passed to the partial with the view_context reference
the_controller.instance_variable_set "#cache_key", cache_key
the_controller.instance_variable_set "#the_object", MyModel.first
view_renderer = ActionView::Renderer.new the_controller.lookup_context
view_renderer.render the_controller.view_context, {partial: 'my_model/the_partial', layout: false}
end
end
This works in Rails 3.2.13.

I think following link should give you a good start.
How do I get the rendered output of a controller's action without visiting the web page?
I try to accomplish the same and as far i can see, your fake request should have the correct host, because the cache-key includes host informations.
I accomplished caching by using ActionController::Integration::Session
ais = ActionController::Integration::Session.new
ais.host = host
ais.xml_http_request(:post, url, params, headers)
I'v got another one:
class FakeRequest
include ActionController::UrlWriter
def initialize(url, params, session, host)
#url = url
#params = params
#session = session
default_url_options[:host] = URI.parse(host).host
end
def post
process(:post)
end
def get
process(:get)
end
def xhr
process(:post, true)
end
def process(method, ajax = false)
uri = URI.parse(url_for(#url))
request = ActionController::TestRequest.new({'HTTP_HOST' => uri.host,'rack.input' => '','rack.url_scheme' => 'http'})
request.query_parameters = #params
request.path = uri.path
request.host = uri.host
request.env['REQUEST_METHOD'] = method.to_s.upcase
if ajax
request.headers['X-Requested-With'] = 'XMLHttpRequest'
end
#session.each_pair do |k,v|
request.session[k] = v
end
response = ActionController::TestResponse.new
controller = ActionController::Routing::Routes.recognize(request).new
return controller.process(request, response)
end
end
This will also return the response object.

Related

How can I use a function defined in a controller in rails in a rake task?

I have an API function defined in my rails controller, and another to my database that I create using scaffold.
`
def function
#results = HTTParty.get( $baseurl + "/extention", :headers => {
$apikey => $apivalue,
"Content-Type" => "application/json"
})
render json: #results.body
end
`
I have defined a rake task that is executed with clockwork but to make it work in the current development environment I had to use httparty to call it internally and work with the received hash.
`
def rake_function
#results = HTTParty.get("http://localhost:3000/controller/extension/")
return #results.parsed_response
end
In addition, my task makes a post and a put when the execution is finished and I must also use a httparty for that.
if (!datExist)
#response = HTTParty.post("http://localhost:3000/controller/extension/",
body: eodData
)
else (datExist)
checkId = dbData.select {|x| x["date_time"] == yesterday}
id = checkId[0]["id"].to_s
#response = HTTParty.put("http://localhost:3000/controller/extension/" + id,
body: eodData
)
end
`
I know it's not the optimal way so I would like to be able to execute the function already defined in my controllers in my rake task
You don't.
The only public methods of your controller should be the "actions" that are called by the Rails router when it responds to HTTP requests.
Controllers are Rack applications and have a hard dependency on a incoming HTTP request - they just don't work outside of that context like in Rake task and using your controllers as a junk drawer leads to the "Fat Controller" anti-pattern.
Controllers already have tons of responsibilites - they are basically stiching your entire application together by passing user input to models and models to views. Don't give them more jobs to do.
Its a very easy problem to avoid by simply moving your API calls into their own class. One way of designing this is through client classes which are just resposible for communicating with the API:
class MyApiClient
include HTTParty
format :json
base_url $baseurl # Code smell - avoid the use of globals
attr_reader :api_key, :api_value
def initalize(api_key:, api_value:)
#api_key = api_key
#api_value = api_value
end
def get_extension
# I don't get why you think you need to dynamically set the header key
self.class.get('extension', headers: { api_key => api_value })
end
end
This lets you simply re-use the code between your controller and rake task and isolates the code touching the application boundy which avoids creating a tight coupling between your application and the external collaborator.
HTTParty also really shines when you actually use it for object oriented and not proceedural code.

Access variable of one controller from another controller rails

In my rails app I have answer_controller and connect_controller.
app >controllers >answer_controller and app >controllers >connect_controller
My connect_controller has below code.
#url = api_version_root+'/members/all?council='+session[:council]
response = RestClient.get #url, api_token_hash
if response.code == 200
#members = JSON.parse(response.body)
end
I want to access #members within answer_controller. How can I do this.
A controllers instance variables are only present for a single request and only one controller action is called per request in Rails by design. This is also generally true of any framework/platform. If you want to persist anything between requests you need to either pass it along or store it somewhere as the thread responding to a request is terminated when it finishes serving a request and its variables are gone with it.
There are many ways to pass data from back and forth between the client and server like query string parameters, cookies and the session (stored in cookies by default). The size is strongly limited by the client such as the cookie size limit of roughly 4096 bytes and the defacto size limit of 2000 characters for URLs.
You can persist data on the server by using rails built in cache mechanism, a database, memory based storage (such as Memcached and Redis) or the servers file system.
Which to use depends on exactly how its being used, the size of the data and what architecticure you have in place.
[MVC Model] On request, just one Controllers#action is called.
If you want to reuse a variable, put it to session
session[:members] = #members
I would use Rails caching, but you must think carefully about how to expire the cache. So:
#members = cached_members
private
def cached_members
#url = api_version_root+'/members/all?council='+session[:council]
Rails.cache.fetch("#{#url}/#{api_token_hash}/members", expires_in: 48.hours) do
response = RestClient.get #url, api_token_hash
(response.code == 200) && JSON.parse(response.body)
end
end
Then duplicate this in the answer controller, and #members will be populated from the cache. Now, of course it's poor practice to actually duplicate code, so pull it out into a helper mixin, like:
module MemberCache
def cached_members
#url = api_version_root+'/members/all?council='+session[:council]
Rails.cache.fetch("#{#url}/#{api_token_hash}/members", expires_in: 48.hours) do
response = RestClient.get #url, api_token_hash
(response.code == 200) && JSON.parse(response.body)
end
end
end
and include this in the two controllers:
require 'member_cache'
class ConnectController < ApplicationController
include MemberCache
end

Stuck in parsing URL and work with it in a view

Trying to parse a URL with this format http://landing.com?data=123 - I'm been able to get the Data through irb like:
require "addressable/uri"
uri = Addressable::URI.parse("http://landing.com?data=123")
uri.query_values['data']
=> '123'
But I'm stuck on how to interact with that 'data' within a Rails view. I have tried including it in Controller (pages_controller.rb in my sample) like:
class PagesController < InheritedResources::Base
def test
uri = Addressable::URI.parse("<%= request.original_url %>")
u = uri.query_values['data']
end
end
But no idea how can I extract that piece of data to be used within my Views. Any guidance on this?
If I open one of the views like where I call that 'test' method - I'm getting uninitialized constant PagesController::Addressable but made sure it's in my enviroment with gem which addressable/uri
Controllers have a lot of the query information already parsed. You can access it with params. In that case, you can use
u = params[:data]
As Sophie Déziel said, if it's under an app request, you can access to your query values through params hash. params is present in your controllers and views.
If you are talking about hardcoded URLs or URLS that you get from 3rd party sources, you will nee to create an instance variable in your controller (#u = ...) to be available in your views.
Note that you're not supposed to call action methods in your views, they are 'invoked' by Rails framework.
# controller
def my_action
# .....
#u = uri.query_values['data']
end
# view
<%= #u %>

Persist job results with Resque / Rails

In my Rails app, I'm trying to take my working API calls and have them handled by background workers.
I have the following in app/jobs/api_request_job.rb:
class ApiRequestJob
def self.perform(params)
Query.new(params).start
end
end
The Query class is where the HTTParty requests are being executed (there are lots of methods for different query types with the same basic format as the parks method:
require 'ostruct'
class Query
include FourSquare
attr_reader :results,
:first_address,
:second_address,
:queries,
:radius
def initialize(params)
#results = OpenStruct.new
#queries = params["query"]
#first_address = params["first_address"]
#second_address = params["second_address"]
#radius = params["radius"].to_f
end
def start
queries.keys.each do |query|
results[query] = self.send(query)
end
results
end
def parks
category_id = "4bf58dd8d48988d163941735"
first_address_results = FourSquare.send_request(#first_address, radius_to_meters, category_id)["response"]["venues"]
second_address_results = FourSquare.send_request(#second_address, radius_to_meters, category_id)["response"]["venues"]
response = [first_address_results, second_address_results]
end
And, finally, the controller. Before trying to farm this action out to background workers, this line was working fine: #results = Query.new(params).start
class ComparisonsController < ApplicationController
attr_reader :first_address, :second_address
def new
end
def show
#first_address = Address.new(params["first_address"])
#second_address = Address.new(params["second_address"])
if #first_address.invalid?
flash[:notice] = #first_address.errors.full_messages
redirect_to :back
elsif Query.new(params).queries.nil?
flash[:notice] = "You must choose at least one criteria for your comparison."
redirect_to comparisons_new_path(request.params)
else
#queries = params["query"].keys
#results = Resque.enqueue(ApiRequestJob, params) # <-- this is where I'm stuck
end
end
end
I'm running redis, have resque installed, and am running the task/starting the workers. The current value being returned for #results is true instead of the hash of results I was need to get back. Is there a way to have the results of the Resque job persist and return data instead of true? What am I missing about how to get background workers to return the same type of data my regular api calls were returning?
Many thanks in advance!
The true you are receiving means the job was scheduled enqueued successfully. The worker will pick it up and run it on the background asynchronously, which means, not at same time as the thread that enqueued it. So there's no way to retrieve the returned value from the job.
If you need the value from that process, you have to run it from the controller without the worker. Also, you wouldn't gain anything from just pushing the work to be done by another process as the web process would have to wait for the response to then keep going anyway.
If you need that returned value right away and are doing this for performance reasons, then you could look into other forms of concurrency, like having another thread doing the request and then only grabbing the result when you need it on the view like:
class AsyncValue
def initialize(&block)
#thr = Thread.new(&block)
end
def value
#thr.join
end
end
on the controller
#results = AsyncValue.new { Query.new(params).start }
and on the view
<%= #results.value.each .... %>
but you'd still have to work around error handling which can get pretty complicated, but is doable.
Personally, I'd just make the request in place, but you know your domain better than me.

Delaying render_to with Resque

I am trying to replicate the setup Ryan Bates has in this railscast on Resque, where he queues up a third party service web request and then updates his results page with results.
I am designing an application that will interact with another Rails app, not a browser, and would like to replicate analogous behavior, with key difference being that only JSON output is expected
Currently I have something like this: (my models are Lists and Tasks, a List has_many Tasks and a Task belongs_to a List.
My lists_controller.rb
def show
Resque.enqueue(TaskDataFetcher,params[:id])
# confused if I need to have a render_to below this.
end
In task_data_fetcher.rb
require "net/http"
require "uri"
class TaskDataFetcher
#queue = :tasks_queue
def self.perform(id)
list = List.new(:id => id)
url = "taskservice.com/" + id + ".json"
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
task = Task.new(:contents => response.body)
task.list = list
# how to return this to the requesting server????
end
end
In the Railscast you see that result doesn't automatically update after the Resque task finishes, he has to reload the page several times, re-making the show request. So if you want to replicate this behaviour you could do something like:
def show
list = List.find(params[:id])
if list
respond_to do |format|
format.json {render :json => list.to_json}
end
else
Resque.enqueue(TaskDataFetcher, params[:id])
render :nothing => true, :status => 202
end
end
Requerement:
So your user is requesting your service to see some tasks. And you have to fetch those from another service taskservice.com. Then i think you have to do this through database for persistency.
Suggestion:
You can have a model like TaskRequest having attributes
`id` # must ;)
`task_list` # might be xml or whatever format suits you best
`is_received` # boolean
In your show method,
You create a TaskRequest entry and render a view which will show a loading type thing and will be requesting for task via ajax. The ajax response should return the task list and the is_received. However, once you get is_received true with a content you should request again.
In parallel, your TaskDataFetcher should receive two ids. One that you are sending now and another is of TaskRequest id. So after fetching the data from the service it will store that in the TaskRequest table and will update the is_recieve to true. Setting it true will eventually turn off requesting for this data anymore.
well the whole explanation might seem a bit hazy. Just let me know if you didnt any part or you need anything else specifically.
Note: It is something like the way SO shows the code formatting while answering a question ;)

Resources