Rails - Too much logic in views? - ruby-on-rails

I have an application used by several organizations and I want to check that users of one domain (a.domain.com) cannot edit users of another domain (b.domain.com). My question is where to put the logic, in a before filter or in the view?
View:
<% if #user.websites.detect {|website| website.url == request.host} %>
render :partial => 'form'
<% else %>
render :partial => 'no_access'
<% end %>
Or, in the controller:
before_filter :verify_editable_user, :only => ['edit', 'update', 'delete']
protected
def verify_editable_user
#user = User.find(params[:id], :include => 'websites')
unless #user.websites.detect {|website| website.url == request.host}
render 'no_access'
end
end
In this scenario, the first version feels cleaner to me. However, the second seems more along the MVC scenario. What do you think? Am I way off base? Thanks in advance.

I recommend using the lockdown gem for authorization. (see http://stonean.com/)
The second one is in fact much cleaner.

A couple other authorization gems to check out would be CanCan and acl9.

You shouldn't place logic in your views. Having logic in the controllers and not in the views will actually make your testing easier...

I would recommend before_filter and acl9. Also using presenters to get code out of your views and into a testable ruby object

Related

Rails 2 Render ERb template in controller?

This code in the controller
av = ActionView::Base.new(Rails::Configuration.new.view_path)
av.extend ApplicationHelper
content = av.render(:file => "show", :locals => { :user => #user })
and the show.html.erb have the link_to helper , operation code error
undefined method `url_for' for nil:NilClass
I add av.extend ActionController::UrlWriter in the controller , still error
undefined method `default_url_options' for ActionView::Base:Class
Try:
content = render_to_string(:file => "show", :locals => { :user => #user })
Usually, in Rails, when something is very hard, it is because you are not approaching the problem from an ideal angle. I can't really answer this question directly, other than to advise not to do this. Ideally, view logic should be in the view, and not the controller. With a few rare exceptions, like using a link_to helper in a flash message (which can be easily solved), these concerns should be separated. This does not seem like one of those exceptions. Instead, I would recommend one of the following (slightly more Rails-y) techniques:
Option 1:
It looks like you are trying to render the view for the show action. This can easily be accomplished by using render :action => 'show' (docs). This will not run the code for the action, just use that view.
Option 2
In the event that option 1 is not viable in your situation, you may alternatively consider some variation of the following technique. Render the default view, as normal. Move the existing content of the view into a partial, and your new content into a partial of its own. Then in your view, simply toggle the partial to render based off of an appropriate condition - the existence of #user, for this example: render :partial => #user ? 'new_content' : 'existing_content'. Depending on your application structure, it may be that this can be further simplified by just rendering the same partial from your show view and the view for the action referenced in the question.
I think keeping the various elements of an application isolated into their intended concerns not only makes this easier to develop and maintain by following the principle of least astonishment, but also usually makes the application much easier to test, as well. Sorry for not answering your question - hope this helps, anyway.
I suppose it was called outside controller so I do it this way in Rails 3:
av = ActionView::Base.new(Rails.configuration.paths["app/views"])
av.class_eval do
include ApplicationHelper
include Rails.application.routes.url_helpers
default_url_options[:host] = 'yoursite.com'
def protect_against_forgery?
false
end
end
#result = av.render(:file => "show", :locals => { :user => #user })

Bypassing Controller in Rails

Im using Rails 2.3.5 and I have a model, let's call it Post. I used named scopes to apply different kinds of sorts on Post. For example, in the Post model I have possibility to rank posts by its scores:
named_scope :order_by_acception_rate_desc,
Proc.new { |limit| { :limit => limit, :order => "acception_rate desc" } }
In the Post Controller I have:
def best
#best_posts = Post.order_by_acception_rate_desc(10)
end
In the view I just render this collection #best_posts:
<%= render :partial => "post", :collection => #best_posts
Currently my application is working like that, but actually I do not need to have the method "best" in the Controller, and I could move it to the Model Post doing like:
def self.best
self.order_by_acception_rate_desc(10)
end
and then in the view I would render the collection like:
<%= render :partial => "post", :collection => Post.best
I do not know which approach is better, but using the ranking methods in the Model, I could avoid to create routes for each one of ranking methods. What approach is better, is there any better approach than these?
with according to Rails conventions the logic should be separated,
controllers handle permissions, auth/authorization, assign instance/class variables
helpers handle html logic what to show/hide to user
views should not provide any logic, permissions check. think about it from designer's point of view
models handle data collection/manipulation over ORM
I'd like to ask you to try:
#helper
def self.best(limit)
all(:limit => limit, :order => "acception_rate desc")
end
#controller
#best_posts = Post.best
#view
<%= render :partial => "post", :collection => #best_posts %>
You should not bypass the controller and include much logic in your view.
You could keep a single route and filter the Post model depending on one of the params.
You don't tell enough here to answer more clearly but you have the big picture.
You can leave just the view file and it should work.

How to use partial in views with different alias MIME?

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] %>

creating dynamic helper methods in rails

I am trying to create a bunch of dynamic helper methods like these:
show_admin_sidebar
show_posts_sidebar
show_users_sidebar
So far I have this in my helper.rb file:
#spits out a partial
def show_sidebar(name, show_sidebar = true)
#content_for_sidebar = render :partial => "partials/#{name}"
#show_sidebar = show_sidebar
end
def show_sidebar?
#show_sidebar
end
In my application layout file I have this: (NB - I'm using HAML):
- if show_sidebar?
= yield(:sidebar)
This allows me to say the following in my views:
- show_sidebar(:foo)
- show_sidebar(:bar)
And this renders the desired partial.
The problem with this is that I can only add one sidebar per page. So, I figure I need to have dynamic methods like: show_admin_sidebar, show_foo_sidebar.
So I have tried to do this:
def show_#{name}_sidebar(show_sidebar = true)
#name = name
#content_for_#{#name}_sidebar = render :partial => "partials/#{#name}"
#show_sidebar = show_sidebar
end
and then in my layout:
- if show_sidebar?
= yield("{#name}_sidebar")
But rails does not like this at all.
I have tried almost everything I can think of in my helper file and nothing works.
The reason I am using helper methods for this is because I want my content div to be 100% page width unless there is a sidebar present in which case the main content goes into a smaller div and the sidebar content goes into it's own..
If I can't get this working, then I can easily fix the problem by just adding the partials manually but I'd like to get my head round this....
Anyone got any experience with this kind of thing?
The entire approach to this was bizarrely overcomplicated, didn't follow Rails conventions at all, nor make the slightest bit of sense, and shame on prior respondents for enabling this approach instead of helping him to simplify. My apologies for being 13 months late with the answer.
Your controller should be deciding if a sidebar is to be shown or not, and setting an instance variable #side_bar_name to either nil or a sidebar name string. Then somewhere in shared view code, probably views/layouts/application.html.erb, you would have something as simple as this:
<% if #side_bar_name %>
<%= render :partial => "partials/#{#side_bar_name}" %>
<% end %>
Or better yet:
<%= render(:partial => "partials/#{#side_bar_name}") if #side_bar_name %>
If you want to use a helper (which is not a bad idea for keeping your code DRY and readable) it would basically be the same code, just moved into the helper.
<%= side_bar_helper %>
def side_bar_helper
render(:partial => "partials/#{#side_bar_name}") if #side_bar_name
end
What the controller does is up to you. It would probably do something like this:
if session[:show_side_bar]
# maybe use cookies instead of session, or store user preference in a database
#side_bar_name = session[:side_bar_name]
end
Here is a solution for you, however I wouldn't suggest too much metaprogramming:
#Add the following snippet to the proper helper module:
['admin','user','whatever'].each do |name|
class_eval{
"def show_#{name}_sidebar(show_sidebar = true)
#name = #{name}
#content_for_#{#name}_sidebar = render :partial => 'partials/#{#name}'
#show_sidebar = show_sidebar
end"
}
end
def show_#{name}_sidebar(show_sidebar = true)
That doesn't look like valid Ruby to me. Are you parsing and evaling this yourself or just throwing that right in the file and expecting it to work?

How to display content that differs across controllers and methods in application layout?

I'm trying to display a javascript feedback widget in my default application.rhtml in a rails app. It will only appear on a subset of pages, distributed across different controllers.
Trying to figure out the best way to do this.
One thought was to do something like this:
<%= render :partial => "layouts/feedback_tab" if #show_feedback_tab == true %>
and then setting #show_feedback_tab in every method in every controller. this seems overly complex. my second thought was that i could default #show_feedback_tab to true and set it to false for the relevant individual methods where i don't want to show it. but a global var doesn't seem right, and a method in application_controller won't work (i think) as the display is dependent on the method that's being called.
Any thoughts?
You can write method in application_controller.rb:
def show_feedback_tab?
if params[:controller] == :user && params[:action] == :index
return true
end
...
or put here any other logic
...
false
end
and add it as a helper method (in application_controller.rb):
helper_method :show_feedback_tab?
then you can use it in views like this:
<%= render :partial => "layouts/feedback_tab" if show_feedback_tab? %>

Resources