Rendering partials / view in a rake task / background job / model in Rails 4
Here's what I compiled from lots of sources and what works for me in Rails 4.
With this Renderer class, you should be able to render Rails 4 views and partials in any context, like background jobs, service objects, models, workers, you name it.
# app/services/renderer.rb
# Render views and partials in rake tasks,
# background workers, service objects and more
#
# Use:
#
# class MyService
# def render_stuff
# result = renderer.render(partial: 'tweets/tweet', locals: {tweet: Tweet.first})
# # or even
# result = renderer.render(Tweet.first)
# end
#
# private
#
# def renderer
# @renderer ||= Renderer.new.renderer
# end
# end
#
class Renderer
def renderer
controller = ApplicationController.new
controller.request = ActionDispatch::TestRequest.new
ViewRenderer.new(Rails.root.join('app', 'views'), {}, controller)
end
end
# app/services/view_renderer.rb
# A helper class for Renderer
class ViewRenderer < ActionView::Base
include Rails.application.routes.url_helpers
include ApplicationHelper
def default_url_options
{host: Rails.application.routes.default_url_options[:host]}
end
end
Update:
There seems to be an easier solution: http://makandracards.com/makandra/17751-render-a-view-from-a-model-in-rails
ApplicationController.new.render_to_string(
:template => 'users/index',
:locals => { :@users => @users }
)
# Mind the weird syntax to set @ variables in :locals.
Update 2:
There's a gem called render_anywhere
that allows for calling "render" from anywhere: models, background jobs, rake tasks, etc.
Update 3:
In Rails 5, the renderer has been extracted and can be used standalone from background jobs and other places:
ApplicationController.renderer.render(
partial: 'messages/message',
locals: { message: message }
)
For Rails <= 4.2, this functionality can be backported with the backport_new_renderer gem.
Rails, How to render a view/partial in a model
proper solution
Well, "they" are right. You really have to do the rendering in a controller -
but it's fair game to call that controller from a model! Fortunately, AbstractController
in Rails 3 makes it easier than I thought. I wound up making a simple
ActionPusher class, working just like ActionMailer. Perhaps I'll get ambitious and
make this a proper gem someday, but this should serve as a good start for anyone else in my shoes.
I got the most help from this link: http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/
in lib/action_pusher.rb
class ActionPusher < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
include Rails.application.routes.url_helpers
helper ApplicationHelper
self.view_paths = "app/views"
class Pushable
def initialize(channel, pushtext)
@channel = channel
@pushtext = pushtext
end
def push
Pusher[@channel].trigger('rjs_push', @pushtext )
end
end
end
in app/pushers/users_pusher.rb. I guess the require could go somewhere more global?
require 'action_pusher'
class UsersPusher < ActionPusher
def initialize(user)
@user = user
end
def channel
@user.pusher_key
end
def add_notice(notice = nil)
@notice = notice
Pushable.new channel, render(template: 'users_pusher/add_notice')
end
end
Now in my model, I can just do this:
after_commit :push_add_notice
private
def push_add_notice
UsersPusher.new(user).add_notice(self).push
end
and then you'll want a partial, e.g. app/views/users_pusher/add_notice.js.haml, which could be as simple as:
alert('#{@notice.body}')
I guess you don't really need to do it with Pushable inner class and the .push
call at the end, but I wanted to make it look like ActiveMailer. I also have a
pusher_key method on my user model, to make a channel for each user - but this
is my first day with anything like Pusher, so I can't say for sure if that's the right
strategy. There's more to be fleshed out, but this is enough for me to get started.
Good luck!
(this was my first draft answer, leaving it in because it might help someone)
I've got the general outline of a solution working. Like this, in your model:
after_create :push_new_message
private
def render_anywhere(partial, assigns = {})
view = ActionView::Base.new(ActionController::Base.view_paths, assigns)
view.extend ApplicationHelper
view.render(:partial => partial)
end
def push_new_message
pushstring = render_anywhere('notices/push_new_message', :message_text => self.body)
Pusher[user.pusher_key].trigger!('new_message', pushstring)
end
that is definitely working - the template is rendering, and gets eval()'ed on the client side successfully. I'm planning to clean it up, almost certainly move render_anywhere somewhere more general, and probably try something like this
I can see that pushes will need their own templates, calling the generally available ones, and I may try to collect them all in one place. One nice little problem is that I sometimes use controller_name in my partials, like to light up a menu item, but I'll obviously have to take a different tactic there. I'm guessing I might have to do something to get more helpers available, but I haven't gotten there yet.
Success! Hooray! This should answer your question, and mine - I'll add more detail if it seems appropriate later. Good luck!!!!
original non-answer from an hour ago left for clarity
I don't have an answer, but this timely question deserves more clarification, and I'm hoping to get closer to my answer by helping ask :)
I'm facing the same problem. To explain a little more clearly, Pusher asynchronously sends content to a connected user browser. A typical use case would be a showing the user they have a new message from another user. With Pusher, you can push a message to the receiver's browser, so they get an immediate notification if they are logged in. For a really great demo of what Pusher can do, check out http://wordsquared.com/
You can send any data you like, such as a JSON hash to interpret how you like it, but it would be very convenient to send RJS, just like with any other ajax call and eval() it on the client side. That way, you could (for example) render the template for your menu bar, updating it in its entirety, or just the new message count displayed to the user, using all the same partials to keep it bone-DRY. In principle, you could render the partial from the sender's controller, but that doesn't make much sense either, and there might not even be a request, it could be triggered by a cron job, for example, or some other event, like a stock price change. The sender controller just should not have to know about it - I like to keep my controllers on a starvation diet ;)
It might sound like a violation of MVC, but it's really not - and it really should be solved with something like ActionMailer, but sharing helpers and partials with the rest of the app. I know in my app, I'd like to send a Pusher event at the same time as (or instead of) an ActionMailer call. I want to render an arbitrary partial for user B based on an event from user A.
These links may point the way towards a solution:
- http://blog.choonkeat.com/weblog/2006/08/rails-calling-r.html
- How to render a Partial from a Model in Rails 2.3.5
- http://mattwindsurfs.wordpress.com/2008/06/19/rails-render-in-a-model/
- http://davetroy.blogspot.com/2008/02/actsasrenderer-brings-output-to-models.html
- https://github.com/asapnet/acts_as_renderer
- http://ethilien.net/archives/render-rails-templates-anywhere-even-in-a-model/
The last one looks the most promising, offering up this tantalizing snippet:
def render_anywhere(partial, assigns)
view = ActionView::Base.new(Rails::Configuration.new.view_path, assigns)
ActionView::Base.helper_modules.each { |helper| view.extend helper }
view.extend ApplicationHelper
view.render(:partial => partial)
end
As does this link provided by another poster above.
I'll report back if I get something working
tl;dr: me too!
Rails 4: why is one way of rendering partials so much faster?
example 1:
you are rendering a partial x times (depending on events).
which means you are compiling html x times(once each time the loop runs). which is slow
example 2:
you are rendering one partial with a collection of events
the html is compiled once(as there is only one partial). which is fast
Rails 5 rendering partials and passing data
Your questions are answered in the Ruby on Rails Guides on Layouts and Rendering. It's worth reading the information on partials that comes before the quoted passages below as well:
Every partial also has a local variable with the same name as the
partial (minus the underscore). You can pass an object in to this
local variable via the :object option:<%= render partial: "customer", object: @new_customer %>
Within the customer partial, the customer variable will refer to
@new_customer from the parent view. (Earlier the Guide instructs that to specify other options for render(), e.g. object:, you have to explicitly specifypartial:
and the name of the partial.)If you have an instance of a model to render into a partial, you can
use a shorthand syntax:<%= render @customer %>
Assuming that the @customer instance variable contains an instance of
the Customer model, this will use _customer.html.erb to render it and
will pass the local variable customer into the partial which will
refer to the @customer instance variable in the parent view.3.4.5 Rendering Collections
Partials are very useful in rendering collections. When you pass a
collection to a partial via the :collection option, the partial will
be inserted once for each member in the collection:index.html.erb:
<h1>Products</h1>
<%= render partial: "product", collection: @products %>
_product.html.erb:
<p>Product Name: <%= product.name %></p>
When a partial is called with a pluralized collection, then the
individual instances of the partial have access to the member of the
collection being rendered via a variable named after the partial. In
this case, the partial is _product, and within the _product partial,
you can refer to product to get the instance that is being rendered.There is also a shorthand for this. Assuming @products is a collection
of product instances, you can simply write this in the index.html.erb
to produce the same result:<h1>Products</h1>
<%= render @products %>
Rails determines the name of the partial to use by looking at the
model name in the collection. In fact, you can even create a
heterogeneous collection and render it this way, and Rails will choose
the proper partial for each member of the collection:index.html.erb:
<h1>Contacts</h1>
<%= render [customer1, employee1, customer2, employee2] %>
customers/_customer.html.erb:
<p>Customer: <%= customer.name %></p>
employees/_employee.html.erb:
<p>Employee: <%= employee.name %></p>
In this case, Rails will use the customer or employee partials as
appropriate for each member of the collection.In the event that the collection is empty, render will return nil, so
it should be fairly simple to provide alternative content.<h1>Products</h1>
<%= render(@products) || "There are no products available." %>
Print file path when rendering a partial in Rails Views
If you really want debugging be eased by getting partial rendering information you can use gem "xray-rails"
group :development do
gem 'xray-rails'
end
Then bundle and delete your cached assets:
$ bundle && rm -rf tmp/cache/assets
Restart your app, visit it in your browser, and press cmd+shift+x (Mac) or ctrl+shift+x to reveal the overlay ( Documentation)
In this way you can debug all your partials when they are rendered.
Hope this Helps..
Rails: Rendering Models?
If you pass a model directly to render it will attempt to render a partial for it.
<%= render @thing %>
That is the same as.
<%= render :partial => 'things/thing', :object => @thing %>
If you pass an array of models...
<%= render @things %>
It will render the _thing
partial for each as if you did this.
<%= render :partial => 'things/thing', :collection => @things %>
Note: this requires Rails 2.3. If you have earlier versions of Rails you'll need to use the :partial option to do the same thing.
<%= render :partial => @thing %>
How can I render my defined model in my view?
It should be:
<%= event.open_student_spots %>
1) Use the = to denote output.
2) It's a method on the model, so you should call it on the event instance. (Which itself should perhaps be an instance variable rather than a local variable)
conditional formatting in rails partials
You could use the Cycle helper. Something like this:
<tr class="<%= cycle("even", "odd") %>">
<td><%= row.name %></td>
</tr>
Or in your case use bgcolor instead, although i would recomend using css classes.
You can cycle through more than two values: cycle(‘first’, ‘second’, ‘third’, ‘and_more’).
There is also: reset_cycle(‘cycle_name’) This makes sure that on each iteration, you will start again with your first value of the cycle list.
Check the rails documentation for more examples.
Avoid using instance variables in partials views
Yes. It is located in a partial file
That's what the warning/error tells you: don't use instance variables in partial files. Rather, use local variables. Example:
Your partial render looks something like this:
= render 'my_partial'
Change it like this to pass all variables explicitly:
= render 'my_partial', productrecords: @productrecords, clone_record: @clone_record
And your partial will use the locals now:
= f.association :product, :collection => Category.products,:include_blank => "Select Product",
:label => "* Product",
:selected =>productrecords.product_id ? productrecords.product_id : clone_record.product_id
Related Topics
Generate a Migration File from Schema.Rb
Sending a Post Request with Net/Http
How to Do Basic Authentication Over Https in Ruby
Cucumber, Capybara and Selenium - Submitting a Form Without a Button
Errno::Enoent: No Such File or Directory Ruby
Carrierwave: Create the Same, Unique Filename for All Versioned Files
How to Get the Ruby Documentation from the Command Line
How to Test a Ruby Application Which Uses Mechanize
Rails Scope Find with Current User
What Are the Uppercase and Lowercase Rules of Ruby Method Name
Rails Plugin for API Key + Secret Key Signing
How to Pass Arguments from the Parent Task to the Child Task in Rake