How to use partial in views with different alias MIME? - ruby-on-rails

Im using 2 different sets of views for 2 different user's roles.
Im using register_alias :
Mime::Type.register_alias "text/html", :basic
in the controller:
class SomeController < ApplicationController
def index
# …
respond_to do |format|
format.html # index.html.erb (advance)
format.basic # index.basic.erb
end
end
end
In some case I have to use the same code in both views, then I would use a Partial, but because of the MIME alias, I have to use 2 identical partials:
my_partial.html.erb and my_partial.basic.erb
I think there is a solution to DRY the code and use only a partial.
Do you have some solutions ?
thank you,
Alessandro

Old Answer:
I probably tried 50 different things until I figured out the right way of writing the partial once, but it was worth it because it's super simple:
Inside your index view, you normally do:
<%= render "my_partial" %>
This implicitly gets mapped to the partial corresponding to the Mime you requested, so it implies having two partial implementations. If you want a DRY partial, simply explicitly specify the format:
<%= render "my_partial.html" %>
As an added bonus of this observation, if your responds_to block of code is really just to switch based on the format and has no logic inside it, you can entirely remove that block of code and things still work implicitly.
Rails 3.2 update:
Rails has deprecated support for the above and support has been completely removed in the latest version of Rails. The following is the correct way as of Rails 3.2:
<%= render :partial => "my_partial", :formats => [:html] %>

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.

Ruby on Rails views names dependences

May be my question it's a little weird for RoR developers, but i'm new in Ruby on Rails, and i only discover this world now - there is some dependencies in views names and definitions in controller?
If i have, for example, view called "parse-public-profile.html.erb" , should i add in controller definition with exactly this name? i mean "def parse-public-profile ... end"
I know, that this is basic, but simply i try to understand how controller knows, what views i have now; what i should change, if i will add/change-name of view, or how to define view, if in my "views" folder, i have another folder, for ex. "clients"
Thanks!
Rails follows REST this means methods as index, show, edit, update, destroy etc. are very common in an Rails controller. When you have a custom action(method) however on your controller Rails will look for the corresponding view file, so for example:
class UsersController < ApplicationController
def another_action
end
end
will try to render: app/views/users/another_action.html.erb
There is also the concept of partials which are normally called within a view file f.e. in users/index.html.erb
<% render :partial => 'form' %>
will try to render: app/views/users/_form.html.erb (note the _)
An in depth explanation can be found in the Rails guides
You can also use:
def index
render :template => "users/parse-public-profile"
end
The :template over rides the default file that Rails would have rendered.
For more info, see the Rails Guide on Layouts and Rendering at http://guides.rubyonrails.org/layouts_and_rendering.html.

Rails implicit render in templates from multiple controllers

I have code like render #posts to render my posts collection in an index template which the PostsController renders.
Now I have an Admin::PostsController that also should render the collection but when my posts controller renders #posts it looks for the admin/posts/_post.html.erb partial. Do I now have to write the partial path explicity? Is this feature by design or a bug? It doesn't seem to make sense.
Yes, you need to supply the path explicitly. And yes, this is by design.
It actually makes sense because Rails is a MVC framework and if you create a controller under a different namespace one would expect separate views for that controller too. Think about convenience, if you wanted to quickly bootstrap an application with a few simple commands, an application where there's a public view of posts and an admin view where all of the admin goodies for editing are, you would EXPECT to have a different directory to store all that admin views.
render #posts is a shortcut for a longer method signature.
In case of PostsController, it is a short cut for render :partial => "post", :collection => #posts; the partial is _post.html.erb and it is expected to be in app/views/posts folder.
In case of Admin::PostsController, it is a short cut for render :partial => "admin#post/post", :collection => #posts; the partial is _post.html.erb, and it is expected to be in app/views/admin/posts folder.
If you want a different partial to be used, you should specify it explicitly.
See the Rendering Collections section of Rails Guides page on Layouts & Rendering for detailed explanation.

Rails partials with single-table inheritance

I want to use partials in rails along with single-table inheritance. I currently have this working:
render partial: #vehicle
# which renders the relevant view, depending on object type, eg:
# views/trucks/_truck.haml
# views/car/_car.haml
I want to leave these default views in place, and create an additional compact view for each object, perhaps like this
# example code only, I want to write something like:
render partial: 'compact', locals: {vehicle: #vehicle}
# and then have this render (for example) with
# views/trucks/_compact.haml
# views/car/_compact.haml
I can happily rename things or change the file names or locations, but what is the simplest way to support two kinds of views (compact and default)?
There will be many more classes later, so looking for very clean, elegant code.
(rails 3.0.5+ on ruby 1.9.2)
To get exactly what you asked for you should do this:
render partial: "#{#vehicle.type.tableize}/#{#vehicle.type.underscore}", object: #vehicle
and you will get rendered:
views/trucks/_truck.html.haml
and the object will be accessible as:
#truck
There might be a better way, but there is always this approach:
render partial: "#{#vehicle.class.to_s.tableize}/compact", locals:{vehicle: #vehicle}
(or it might need to be _compact, instead of just compact, but you get the idea)
I've done something similar, but rather than having the partials in two separate files, I've combined them, and used the locals argument to pass a flag:
# The hash syntax for render is redundant, you can simply pass your instance
# Render the long-form of your partial
render #vehicle
# When using render _instance_, the second argument becomes the locals declaration
# Render the compact form
render #vehicle, compact: true
And then, in my partial...
<% if defined? compact %>
<!-- HTML for compact view -->
<% else %>
<!-- HTML for extended view -->
<% end %>
The advantages of this approach are that you're only maintaining one partial file for each vehicle type, and your code remains pristine.
The disadvantage is that it's a slight departure from the "traditional" usage of partials.

One controller rendering using another controller's views

I have QuestionController
I now have AnotherQuestionController with actions which should render using templates and partials in app/views/question/
Is this possible? Seems like it should be.
I've tried
render :template => "question/answer"
but answer.html.erb includes partials and I get errors like
"Missing template another_question/_my_partial.erb in view path"
So is there a way to tell Rails "treat AnotherQuestionController as if its QuestionController and look for views and partials in app/views/question"?
Or will I have to create app/views/another_question - which will cause duplication (this can't be the Rails way).
Thanks
Template rendering should actually work
render :template => "question/answer"
The problem you were having is from the partials looking in the wrong place. The fix is simple, just make your partials absolute in any shared templates. For example, question/answer.html.erb should have
<%= render :partial => 'question/some_partial' %>
rather than the usual
<%= render :partial => 'some_partial' %>
You can achieve it with:
render 'question/answer'
Rails uses a list of prefixes to resolve templates and partials. While you can explicitly specify a prefix ("question/answer"), as suggested in another answer, this approach will fail if the template itself includes unqualified references to other partials.
Assuming that you have an ApplicationController superclass, and QuestionController inherits from it, then the places Rails would look for templates are, in order, "app/views/question/" and "app/views/application/". (Actually it will also look in a series of view paths, too, but I'm glossing over that for simplicity's sake.)
Given the following:
class QuestionController < ApplicationController
end
class AnotherQuestionController < ApplicationController
end
QuestionController._prefixes
# => ["question", "application"]
AnotherQuestionController._prefixes
# => ["another_question", "application"]
Solution #1. Place the partial under "app/views/application/" instead of "app/views/question/", where it will be available to both controllers.
Solution #2. Inherit from QuestionController, if appropriate.
class AnotherQuestionController < QuestionController
end
=> nil
AnotherQuestionController._prefixes
# => ["another_question", "question", "application"]
Solution #3. Define the class method AnotherQuestionController::local_prefixes
This was added in Rails 4.2.
class AnotherQuestionController < ApplicationController
def self.local_prefixes
super + ['question']
end
end
AnotherQuestionController._prefixes
# => ["another_question", "question", "application"]
You could try the inherit_views plugin (http://github.com/ianwhite/inherit_views/tree/master) I mentioned here in the answer to this question.

Resources