Dynamic rendering of partials in Rails 3 - ruby-on-rails

In the app I'm working on, I have defined a method task_types in the model:
task.rb
def self.task_types
["ad_hoc"]
end
I use task_types to populate a drop-down menu, but I would also like to use it to dynamically render a partial.
What I have in mind is, for each task type, there will be an associated comment box. I went ahead and made a helper that will call the correct partial depending upon which task type was selected:
#tasks_helper.rb
module TasksHelper
def completion_comment(task)
task_types = #task.task_type
render :partial => "#{Task.task_types}", :locals => {:task => task}
end
end
Unfortunately, when I called completion_comment, I received the error message "The partial name (["ad_hoc"]) is not a valid Ruby identifier."
What I'm looking for is a dynamic way to render the appropriate partial. I think I'm on the right track, but I'm not sure how to extract the array elements from my task_types method (as you can see in the error message, the brackets and quotation marks are getting pulled into the render action). Or, perhaps I need to utilize a different method for dynamically rendering the partial. Any help is appreciated.

Here's what worked:
module TasksHelper
def render_task_form(task)
render :partial => "/tasks/completed/#{task.task_type.downcase}"
end
end
I had tried this solution much earlier, and had received an error that "The partial name (/tasks/completed/) is not a valid Ruby identifier; make sure your partial name starts with a letter or underscore, and is followed by any combinations of letters, numbers, or underscores."
I tried it again, and to remedy this problem I deleted all tasks from the database. Now the partial renders dynamically. Hopefully this helps someone else!

You need a string instead of the entire task_types array. #task.task_type should return a key that a) matches an element in the task types array and b) matches a known partial.
The following is a bit more complicated that it needs to be but should do the trick:
tasks_helper.rb
module TasksHelper
def completion_comment(task)
if Task.task_types.include? task.task_type
render :partial => task.task_type,
:locals => {
:task => task
}
else
# render a generic comment box here
end
end
end

In Rails 4, the view or the partial name is supposed to respond to a valid Ruby identifier.
So, the entire view name must follow the same format as a ruby-identifier:
it should start with a _ or character
it cannot start with a number
it can have only _ and alphanumerics.
So, considering that task_type is a valid ruby identifier (which it should be), it will work. In generate this code will not work in Rails 4
render '/tasks/completed/some-task'
but this will work
render '/tasks/completed/some_task' # note the underscore.

Related

Ruby on Rails basic tips

Im new at Ruby on Rails language and I really need that someone explain me some 'topics' if possible.
I've created an app and I Scaffolded it and it created in the controller lots of code but I have doubts.
One of them is:
This app is 'empty' so far. It only has a 'New Book' in the first page.
//books\index.html.erb
||| <%= link_to 'New Book', new_book_path %> |||
new_book_path redirects me to books_controller
def new
#book = Book.new
respond_to do |format| //-----> What means this 'format'?
format.html # new.html.erb // What really mean two options for 'format'?
format.json { render json: #book } // What means render json: #book
end
# new.html.erb -> has this code inside
New author
/*<%= render 'form' %>
<%= link_to 'Back', authors_path %>*/
Can someone explain me what's hapenning here?
I know that these are really silly questions but I'm not getting it.
Thanks in advance.
The best two ways to understand Rails in-depth are reading its code (https://github.com/rails/rails) and reading Documentation (http://api.rubyonrails.org and http://guides.rubyonrails.org).
So you'll find enough information to cover this topic here: http://api.rubyonrails.org/classes/ActionController/MimeResponds.html or here: http://guides.rubyonrails.org/action_controller_overview.html.
But if you want short answer... listen to the story :)
The entire respond_to do ... end block is responsible for defining rules on how your app should response on different 'formats'. Rails supports a lot of different formats, i.e :html, :json, :xml (you even can define your own formats). Beside mime types it has variants: :desktop, :tablet, :phone. Obviously that with mime types you describe how you want to answer on different types of request and with variants you specify different options for various user agents.
:format variable passed into block has type ActionController::MimeResponds::Collector. They didn't call it so for nothing. It collects all different response types you specify inside block and then using headers section from http request picks an appropriate variant from that options.
Hope it was useful. But again, better check Documentation.
Rails uses MVC pattern in as its foundation ([http://en.wikipedia.org/wiki/Model–view–controller]). So what we've seen before was a Controller. And you can treat new.html.erb as a View for :new action for that controller.
The file itself is an html file flavored with ERB (NOT the same as Epic Rap Battles of History, but [http://en.wikipedia.org/wiki/ERuby]) template engine. ERB is able to inject chunks of ruby code into your pages. <% %> enclosing tag is used just for evaluation and <%= %> for injection of the result of evaluation. So in your case with <%= render 'form' %> you inject result of #render method call into your html and with :link_to helper you create link.
IN CONCLUSION: I recommend you to start with https://www.railstutorial.org. That's an excellent tutorial for starters. You'll find answers for most of your questions and even develop your own little Twitter! (at least 2nd edition is about Twitter).
respond_to is a Rails controller method (explanation here: http://api.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to), which gets a block as argument. In short, block is a part of code ran within method it has been passed to.
For a block you declare variable called 'format'. Because this in just variable name so you may declare it i.e. 'f' or whatever you want.
Within the block of respond_to method, you may declare how your controller action responds for given MIME type. So, for HTML you may leave it empty, however if you want your controller to respond to JSON (MIME: application/json and you define it in the request header from the client side), then you have to tell your controller that's the response has to be in json format.

Render ERB Template in RABL Template

I have a scenario where I'd like to pass back a long message with my JSON. Instead of writing it out with string concatenation I'd rather put together an erb template that I can render into my JSON. Below is the code I'm currently trying:
object #invitation
node(:phone_message) do |invitation|
begin
old_formats = formats
self.formats = [:text] # hack so partials resolve with html not json format
view_renderer.render( self, {:template => "invitation_mailer/rsvp_sms", :object => #invitation})
ensure
self.formats = old_formats
end
end
Everything works as expected the first time this code is run, however, I run into problems the second time I run it because it says there is a missing instance variable (which I assume was generated and cached during the first run).
undefined method
_app_views_invitation_mailer_rsvp_sms_text_erb___2510743827238765954_2192068340
for # (ActionView::Template::Error)
Is there a better way to render erb templates into rabl?
You could try using ERB as standalone, and not going through the view renderer, like so:
object #invitation
node(:phone_message) do |invitation|
begin
template = ERB.new(File.read("path/to/template.erb"))
template.result(binding)
end
end
binding is a method on Object (through the Kernel module) and it returns the binding which holds the current context, which also includes instance variables (#invitation in this case)
Update:
Don't really know if this will help you get any further (and I also realised it's been more than a year since you posted this), but here's another way to render ERB templates in a standalone fashion:
view = ActionView::Base.new(ActionController::Base.view_paths, {})
class << view
include ApplicationHelper
include Rails.application.routes.url_helpers
end
Rails.application.routes.default_url_options = ActionMailer::Base.default_url_options
view.render(:file => "path/to/template.html.erb", :locals => {:local_var => 'content'})
When I have time I should actually try this with Rabl.

Rails 3 rendering template missing without file ending

I have an action in my controller as below:
def show
#post = Post.find_by_setitle(params[:setitle])
if !#post
render 'error_pages/404'
return
end
respond_to do |format|
format.html
end
end
If the render error_pages/404 I get a template missing. Switching it to render error_pages/404.haml.html works fine.
Why is this?
N.B. There is no actual error_pages controller or model. Just a convenient place to keep them.
Edit: I'm using mongoid and hence don't have access to ActiveRecord. Controller base can't be looking for a particular ActiveRecord exception?
From the documentation
The render method can also use a view that’s entirely outside of your application (perhaps you’re sharing views between two Rails applications):
Rails determines that this is a file render because of the leading slash character. To be explicit, you can use the :file option (which was required on Rails 2.2 and earlier):
You need either to pass the :file option, or to start the location string with a slash. Alternatively, you could use the Rails functionality to rescue from errors, and recover from ActiveRecord::RecordNotFound with a 404. See this post for details.
You should probably use render :template => 'error_pages/404'.
I think Rails is looking for a partial called _404.
Try it out 1:
render 'error_pages/404' (and name the file _404.html.erb)
Try it out 2:
render :template => 'error_pages/404' (and name the file 404.html.erb i.e. no leading underscore)

Rendering a Heterogeneous Collection: How can I specify a single directory for the partials?

I have a collection, #comments, that is heterogeneous but hierarchical. Each comment is either an instance of Comment or some derived class, like ActionComment or InactionComment. I am rendering a different partial for each type of Comment. The View code is:
= render #comments
As all the partials are related, I would like to keep them in a single view directory, i.e.:
app/views/comments/_comment.haml
app/views/comments/_action_comment.haml
app/views/comments/_inaction_comment.haml
But right now in order to use the automatic rendering of the correct partial, I am using separate directories, like:
app/views/comments/_comment.haml
app/views/action_comments/_action_comment.haml
app/views/inaction_comments/_inaction_comment.haml
Rails 3.2 makes a Model#to_partial_path method available which allows you (as its name suggests) to override the partial pathname.
def to_partial_path
self.action.to_s
end
The path it returns does not include the leading underscore and is assumed to be relative to .../views/modelname/. See http://blog.plataformatec.com.br/2012/01/my-five-favorite-hidden-features-in-rails-3-2/ for an overview
You can't do it quite as magically, but you can do it by just rendering each item individually and specifying the partial.
example in haml:
- #comments.each do |c|
= render :partial => "comments/#{c.class.to_s.underscore}", :locals => {:comment => c}

Ruby On Rails: using arrays with link_to

I was curious on how to use arrays in the link_to method in ruby on rails for example:
Controller:
def index
#test = [1,2,3]
end
View:
<%= link_to "test", {:action => 'index'}, :test => #test %>
When looking at the source then, I end up with something to the effect of:
test
My guess is that the array's to_string or something similar is getting called to set the value of test in the html.
My goal is to be able to have a form in which people can submit data on the page, and then once they've submitted the data and return to the page, if they click on the link the data will persist through clicking on the link.
*Ideally I would like to do this without having to pass the parameters in the url.
Thank you.
If you want to keep data you should probably use cookies. They are very easy to use, just assign a value with the following in the action:
cookies[:some_key] = "some value"
and retrieve it with this:
cookies[:some_key] # returns "some value"
However, just to clarify what link_to is doing in your example:
<%= link_to "test", {:action => 'index'}, :test => #test %>
When looking at the source then, I end up with something to the effect of:
test
The reason is that you are passing #test to the third argument in link_to, which is a hash of html attributes, hence why it's turned into one. To have it become an parameter on the link, you need to pass it with the second, eg, {:action => 'index', :text => #test}. As noted above, however, this is not necessarily the best way to tackle this and, in addition, it's usually best to also pass the controller name to link_to or, better yet, use a named route.
If I understand well, you want to keep the datas submitted by the user after they validate the form ?
Well Rails is able to do that without any of your code line needed.
Based on the supposition that you have a route resource "objects"
In your controller :
def edit
#object = Object.find_by_id params[:id]
end
def update
#object = Object.find_by_id params[:id]
if #object.update_attributes params[:object]
# The datas have been successfully saved. You redirect wherever you want to.
else
render :action => 'edit'
end
end
and in your view :
<% form_for #object do |f| %>
<%= text_field :name %>
<% end %>
When the form fails to validate, the "name" text field automatically gets the previous entered data.
If after that you still need to reload your datas, you don't need to add them as a parameter in a link tag.
You get the object in your controller and passes it's datas to the view where you display it.
I would just write a view helper that formats it into a string with good separators, like commas.
That isn't a good way to be passing along information though. Try session variables, cookies, or url-encoded variables instead.
The best match to what you are doing would be url-encoded variables, which will show up in a form similar to this:
test
My guess is that it is using Array#join.
You could try something like
:test => #test.join( ',' )
and then parse the string in your controller. But it is somewhat error prone if the user enters the same character you chose as delimiter.
But, assuming the linked page is also served by Rails, I think the best solution would be to use the flash area to store the results on the server
flash[ :submitted_params ] = params;
and in the controller for the linked page
old_params = flash[ :submitted_params ] || {}

Resources