Share Data between controller actions Rails - ruby-on-rails

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

Related

How templates streaming works in Rails?

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.

Persisting ActiveRecord objects between requests

I have an ActiveRecord model named Document and have implemented CRUD operations around it. I just have a problem with persisting a Document instance between requests when validation fails (be cause I wanna redirect to another page when this happens).
First, I tried storing the instance in the flash session:
# documents_controller.rb
def new
#document = flash[:document] || Document.new
end
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
With the code above, I was expecting that the actual Document instance was stored in the flash session, but instead it became a string which looks somewhat like #<Document:0xad32368>. After searching online for a while, I found out that for some reasons you cannot store ActiveRecord objects in sessions.
There are a lot of suggestions about just storing the object's id in the flash session, but I can't do that because as you can see, the object is not yet stored in the database.
Next, I tried reconstructing the Document instance after the redirect, taking advantage of the instance's attributes method (which returns a serializeable hash that can be stored in the session):
# documents_controller.rb
def new
#document = Document.new(flash[:document_hash] || {})
end
def create
...
flash[:document_attributes] = document.attributes
redirect_to new_document_path
end
This almost solved the problem, except for the part in which the validation errors (document.errors) are not preserved. Also, if this is used to persist an instance already stored in the database (in the case of failed validations when updating a Document instance), I'm not sure which between the original attributes and the new attributes will get persisted.
Right now I've already run out ideas to try. Anyone who has a decent solution for this?
EDIT:
You might be wondering why I still have to redirect to another page instead of just rendering the new document view template or the new action in the create method. I did so because there are some things in my views that are dependent on the current controller method. For example, I have a tab which needs to be highlighted when you are on the document creation page (done by checking if action_name == "new" and controller_name == "documents"). If I do:
def create
...
render action: "new"
end
the tab will not get highlighted because action_name will now be create. I also can't just add additional condition to highlight the tab if action_name == "create" because documents can also be created from the the index page (documents_path). Documents can also be updated from the index page (documents_path) or from the detail page (document_path(document)), and if validation fails in the update method, I'd like to redirect to the previous page.
If I really need to fake persisting something between requests (all of the variables that you set are lost between requests), I will ususally put the relevant attributes into hidden fields in the new form.
In your case, this is overkill. In your code, you are redirecting, which causes a new request:
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
You can easily render the output of another action, instead of redirecting, by using render action: 'action_to_render'. So in your example, this would probably be:
def create
#document = Document.new(document_params)
if #document.save
render action: 'index'
else
render action: 'new'
end
end
Which can be simplified to:
def create
#document = Document.new(document_params)
action_to_render = #document.save ? 'index' : 'new'
render action_to_render
end
If you need extra logic from the action, you can refactor the logic to a method called from both actions, or simply call the other action from the current one.
It is fine once in a while, but I would caution that having to jerk around with the rendering too much is usually indicative of poor architecture.
Edit:
An additional option, given the newly highlighted constraints, could be to make the new and create methods the same. Remove the new action and routes, and make create answer for GET and PATCH requests. The action might look something like:
def create
#document = Document.new(document_params)
request.patch? && #document.save && redirect_to( documents_path )
end
I actually use something very similar to this for almost all of my controllers, as it tends to DRY things significantly (as you can remove the extra probably identical view, as well)
Another option would be to just use an instance variable to keep track of the active tab in this instance, and make the rest of the code a lot cleaner.
SOLVED
I was able to make a workaround for it using ActiveSupport::Cache::Store (as suggested by #AntiFun). First I created a fake_flash method which acts closely like the flash sessions except that it uses the cache to store the data, and it looks like this:
def fake_flash(key, value)
if value
Rails.cache.write key, value
else
object = Rails.cache.read key
Rails.cache.delete key
object
end
end
And then I just used it like the flash session.
# documents_controller.rb
def new
...
#document = fake_flash[:document] || Document.new
...
end
def create
document = Document.new document_params
...
# if validation fails
fake_flash :document, document
redirect_to new_document_page
end

Define timer method controller rails

I have a method in controller that calls another method created in a module like this example:
def example
#var1 = ModuleName::ClassName.get()
respond_to do |format|
format.json { render json: #var1}
end
end
The method get() goes to a website looking for information and returns an array.
Everything works perfectly, but I wonder if in the controller there is a way to set a timeout if the application takes a long time to run! Is it possible?
here is one way (a more general way) you could do that..
def example
Timeout::timeout(40) do # 40 sec, change it to anything you like
#var1 = ModuleName::ClassName.get()
rescue Timeout::error
# do something (maybe set #var1's value if it couldn't get desired array)
end
respond_to do |format|
format.json { render json: #var1}
end
end
If under ModuleName::ClassName.get() you imply some kind of third-party ruby http library, then it's likely that you should set some kind of timeout parameter (depends on library). You just pass a desired timeout in seconds (or whatever measurement you want).
Thus the pseudo-code might look like this:
ModuleName::ClassName.get(10)
For more detailed answer, can you please be more specific about how are you doing a call to external service?

Backbone.js and Rails - How to handle params from Backbone models?

In a standard Rails controller, I'd create a record like this:
#user = User.new(params[:user])
This assumes that the form parameters that come in are nested.
I've been playing with Backbone.js and I noticed that by default, Backbone doesn't nest the parameters the way a normal Rails form might, which is actually something I expected. So I'm wondering what I should do...
Do I
figure out on the server-side if it's a request from Backbone by looking at accepts headers, etc and manipulate the params myself so I can keep my controller code small:
do_some_params_manipulation_with(params)
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html {redirect_to users_url}
format.json {render :json => #user.to_json }
end
end
Or, do I instantiate the object in each branch which ends up with repeated code but might be more maintainable in the long run....
respond_to do |format|
format.html do
#user = User.new(params[:user])
if #user.save
redirect_to users_url
end
end
format.json do
#user = User.new(params) # and rely on mass-assignment protection
if #user.save
render :json => #user.to_json
end
end
end
or do I modify my Backbone.js models by overriding the .toJSON method (which I'm not entirely sure how to do because I don't know enough about Backbone.js yet) so that it nests the params?
In this situation, I have access to both sides of the app, but I am interested in what others are doing.
It's nice when you can have the general Rails forms and Backbone forms match with respect to the root node. That's why in my last application I chose to override the Backbone models' toJSON method.
You could override the global toJSON method as Raimonds Simanovskis suggested. But even the non-DRY way approach isn't so bad. Just one line of boilerplate for each model definition:
// Depends on Underscore.js
User = Backbone.Model.extend({
toJSON: function() {
return { user: _.clone( this.attributes ) }
},
// Your other methods here
});
Edit: Corrected code sample. Sorry for the errors, I was translating from CoffeeScript to JavaScript.
If you are using the backbone-rails gem, looks like you can do
var User = Backbone.Model.extend({
paramRoot: 'user'
});
Around line 45 on github
Credit PL J and stream 7 on this link
I have made a little hack to namespace save requests under model.name property.
It monkey patches toJSON() during sync() call only and restores original method so you can use it as usual.
I have implemented it in CoffeeScript.
Check it here
It should be noted that if you opt for the currently accepted answer (patching toJSON at the model level) you are affecting reading as well. Maybe that goes without saying, maybe not. But you will have a lot of work to do when rendering models/collections if you put this patch into affect in a backbone app. Therefore, I personally wouldn't use it as-is.
In one of the answers to Rails mass assignment and Backbone.js there is mentioned patch https://gist.github.com/719080 which I think will do what you need.
As of Rails 3.1 there's now a new initializer called wrap_parameters.rb which handles this problem by default. The code that handle this case is:
# Disable root element in JSON by default.
ActiveSupport.on_load(:active_record) do
self.include_root_in_json = false
end
Bada bing!

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?

Resources