In the Rails API documentation, here what is said about streaming templates.
When to use streaming
Streaming may be considered to be overkill for lightweight actions
like new or edit. The real benefit of streaming is on expensive
actions that, for example, do a lot of queries on the database.
In such actions, you want to delay queries execution as much as you
can. For example, imagine the following dashboard action:
def dashboard
#posts = Post.all
#pages = Page.all
#articles = Article.all
end
Most of the queries here are happening in the controller. In order to
benefit from streaming you would want to rewrite it as:
def dashboard
# Allow lazy execution of the queries
#posts = Post.all
#pages = Page.all
#articles = Article.all
render stream: true
end
Notice that :stream only works with templates. Rendering :json or :xml with :stream won't work.
The thing that I do not understand is, how does using stream: true option, will make the queries go through a lazy execution? The queries here are called before the render method, so how all this works?
Those queries will already be lazy by default, regardless of render stream: true. That's just how Model.all works. It isn't until you call a method that triggers the query to actually run (e.g. inspect when you run this code in the rails console). See https://github.com/rails/rails/blob/f0d3c920a5aeb3babc35500e13288e148238b65e/activerecord/lib/active_record/scoping/named.rb#L24-30.
Also, for what it's worth, I believe streamed template rendering is opt-in in rails 3, but the default in rails 4.
Related
I have an application that renders a div that inside of it has links, that updates the look of this div using AJAX. The issue is Rails has to go and request the same data over and over when switching the view which seem inefficient.
After reading up on the subject of sharing variables between controller actions I understand that this is not possible due to the stateless nature. Have also read that session should not be used to store object, and these variables contain lots of data, mainly used to generate graphs. Another option I guess would be Caching which I'm not very familiar with. Or saving the variables in Javascript on the browser possibly.
Anyone had a similar problem that could provide some guiding?
class ArtistsController < ApplicationController
def index
if params[:term]
respond_to do |format|
format.html
format.js {render "show.js.haml"}
end
else
#artists= Artist.all
end
end
def show
#days=90
# DATABASE QUERIES
#artist= Artist.find(params[:id])
#reach_period= Reachupdate.period(#artist.id,#days)
#update_period=Update.period(#artist.id,#days)
#songs_load= Song.by_artist(#artist.id).joins(:playlists)
#song_period= #songs_load.period(#days)
# CHART
#updates= ChartTransformation::pop_chart(#update_period,#days)
#reach = ChartTransformation::reach_chart(#reach_period,#days)
#dates= ChartTransformation::dates(#days,3)
#reach_labels= ChartTransformation::reach_labels(#reach_period,2)
#songs= ChartTransformation::data_to_releases(#song_period, #days)
#reach_diff = Reachupdate.diff(#reach_period)
#pop_diff = Update.diff(#update_period)
#playlisting= ChartTransformation::playlisting(Playlist.by_artist(#artist.id),#days)
end
def overview
#days=90
# DATABASE QUERIES
#artist= Artist.find(params[:id])
#reach_period= Reachupdate.period(#artist.id,#days)
#update_period=Update.period(#artist.id,#days)
#song_period= Song.by_artist(#artist.id).period(#days)
# CHART
#updates= ChartTransformation::pop_chart(#update_period,#days)
#reach = ChartTransformation::reach_chart(#reach_period,#days)
#dates= ChartTransformation::dates(#days,3)
#reach_labels= ChartTransformation::reach_labels(#reach_period,2)
#songs= ChartTransformation::data_to_releases(#song_period, #days)
#reach_diff = Reachupdate.diff(#reach_period)
#pop_diff = Update.diff(#update_period)
#playlisting= ChartTransformation::playlisting(Playlist.by_artist(#artist.id),#days)
respond_to do |format|
format.js {render "timeline_content.js.haml"}
end
end
end
Another option I guess would be Caching which I'm not very familiar with
you will have to make yourself familiar with caching. it's the answer to your question.
in your case, it would do a fragment-caching in the view, read the guides https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching
I have the following boilerplate in a Cars controller
class CarsController < ApplicationController
# GET /cars
# GET /cars.json
def index
#cars = Car.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #cars }
end
end
Let's say I have 50,000 cars in my database.
My understanding is that each call to /cars/index will query the db (select * from cars), and build an object to store all that info. Then the Cars view would have access to #cars, and a json request would simply get a giant json response including all 50k cars.
This default behavior sounds like a horrible idea in my situation. What if /cars is a very popular page? What if I had 10 million cars?
I don't use #cars in the view (index.html.erb), so how can I verify that I don't need
any json calls? (Does Rails need this for some reason I'm unaware
of?)
It seems that #cars = Car.all is a very uncool line of code only used to validate that your new controller is working, and should probably be removed right in the beginning. Right?
Usually you should not want to display all "Cars" on a single page. Some filtering should occur before displaying the results. The most widespread solution would be to "paginate" the results for large datasets. Depending on implementation this should happen before the actual object is fetched from the database.
have a look at
https://github.com/mislav/will_paginate/wiki
Instead of all, you should use #car = Car.scoped. It will return an active record relation which will lazy-load car objects only when they are needed. Highly memory efficient.
For example, if you need only thirty cars from the #cars variable which is supposed to have all cars in database, #cars.limit(30) will load only those many.
On the other hand, if you used #cars = Car.all and then try #cars[0..29], all cars get loaded into #cars anyway which will bloat the memory.
Excuse my english. I am finding it hard to type straight at the moment.
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 ;)
What can people do to optimize the rendering of views in Rails 2.x (and 3.x)?
Our application spends most of its time rendering views (minimal DB calls).
How can we accelerate performance/rendering of views?
Thanks!
The recommended way to speed erb rendering is to avoid doing it. Try using page caching or conditional GET headers to avoid re-rendering content that hasn't changed.
http://guides.rubyonrails.org/caching_with_rails.html
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
# If the request is stale according to the given timestamp and etag value
# (i.e. it needs to be processed again) then execute this block
if stale?(:last_modified => #product.updated_at.utc, :etag => #product)
respond_to do |wants|
# ... normal response processing
end
end
# If the request is fresh (i.e. it's not modified) then you don't need to do
# anything. The default render checks for this using the parameters
# used in the previous call to stale? and will automatically send a
# :not_modified. So that's it, you're done.
end
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?