How much code in a rails view is ok? - ruby-on-rails

I know that it is best to keep code out of the presentation layer. But, I am wondering how much is considered "acceptable". For example I populate an html select box with this line of code.
CodesecureProject.find(:all,:order => 'name').collect {|project| [project.name, project.id] }
Right now I have this line of code embedded in the form. What I am wondering if the community thinks if this is to much code and it should be first stored in an instance variable on the controller then the variable used in the form.

I'm not going to say I'd never do it (I'd be lying) but the code example given would make me nervous. I think I'd be more inclined to deliver the data to the select box from my controller. A helper method is another option if I notice I'm doing something more than once. I'm more likely to see the duplication in the controller than across distinct views.
If I'm using the same HTML component across multiple views, then I might find myself reaching for partials or wrapping the whole thing in a custom helper: project_select() or some such.
The more I work in the MVC world the more I find myself avoiding code in views. I have a feeling that some kind of Zen mastery will be achieved if I reach the zero code state, although the value of that in anything but philosophical terms is highly debatable.

I use the following static method in a Site model to achieve something similar:
class Site
def self.select_options
Site.find(:all, :order => 'UPPER(name)').collect {|s| [s.name, s.id]}
end
def
Then in my Domain view I call:
<%= f.select :site_id, Site.select_options %>
This works really well for these circumstances.
In your instance, you might try:
class CodesecureProject
def self.select_options
CodesecureProject.find(:all, :order => 'name').collect {|p| [p.name, p.id]}
end
end
And then call it through the view with:
<%= f.select :codesecure_project_id, CodesecureProject.select_options %>

I have a lot of the same code in my projects except I try to don't do any finds. In your case I would make an named scope
named_scope :order, lambda { |order| {:order => order}}
and make the code:
CodesecureProject.order(:name).collect {|project| [project.name, project.id] }
It's a little cleaner.
If you got a lot of select boxes which need a name and an id (I sure do sometimes), you could also try making a helper that excepts a ModelName and returns the array you need.
def magic_for_select(model)
model.all.collect{|instance| [instance.name, instance.id]}
end

I would go a bit further than Maran. Generally I do the following:
Create a named_scope in the model to execute the find.
Call the named_scope from the controller and store the results in an instance variable.
Only put the instance variable in the view.
I would only use a helper if absolutely necessary. When going back over your code later, it's easier to make sense of things if you see your controller setting up the data that the view needs, rather than the view calling the helper (yet another file to look at).

Related

Use a params[:value] to reference a controller method in Rails

I currently have a form (using form_tag). One of the fields is a dropdown list of options. Each option value matches the name of a method in my controller. What I want to do is when the form submit button is clicked, it runs the controller method corresponding directly to the value selected in the dropdown field.
I've built a work-around right now, but it feels too verbose:
def run_reports
case params[:report_name]
when 'method_1' then method_1
when 'method_2' then method_2
when 'method_3' then method_3
when 'method_4' then method_4
else method_1
end
# each method matches a method already defined in the controller
# (i.e. method_1 is an existing method)
I had thought that it may work to use the dropdown option value to run the corresponding method in my controller through the form_tag action (i.e. :action => params[:report_name]), but this doesn't work because the action in the form needs to be set before the params value is set. I don't want to use javascript for this functionality.
Here is my form:
<%= form_tag("../reports/run_reports", :method => "get") do %>
<%= select_tag :report_name, options_for_select([['-- Please Select --',nil],['Option 1','method_1'], ['Option 2','method_2'], ['Option 3','method_3'], ['Option 4','method_4']]) %>
<%= submit_tag "Run Report" %>
<% end %>
Any suggestions?
Can I change my controller method to look something like this - but to actually call the controller method to run? I'm guessing this won't run because the params value is returned as a string...
def run_reports
params[:report_name]
end
WARNING: this is a terrible idea
You could call the method via a snippet of code like this in the controller:
send(params[:report_name].to_sym)
The reason this is a terrible idea is that anyone accessing the page could manually construct a request to call any method at all by injecting a request to call something hazardous. You really, really do not want to do this. You're better off setting up something to dynamically call known, trusted methods in your form.
I think you should rethink the design of your application (based on the little I know about it). You have a controller responsible for running reports, which it really shouldn't be. The controllers are to manage the connection between the web server and the rest of your app.
One solution would be to write a new class called ReportGenerator that would run the report and hand the result back to the controller, which would run any of the possible reports through a single action (for instance, show). If you need variable views you can use partials corresponding to the different kinds of reports.
As for the ReportGenerator, you'll need to be a little creative. It's entirely possible the best solution will be to have an individual class to generate each report type.

Ruby On Rails Helpers -- Using instance variables in helpers

I have a helper for a controller:
module CourseStepsHelper
def current_quiz_result
#course_step.step.step_quiz.quiz_attempts.where(:patient_id => current_user.id, :step_quiz_id => #course_step.step.step_quiz.id).first
end
end
It has access to #course_step which is defined in the CourseSteps controller show "action". Is this common practice to use instance variables in helpers?
Depending on the level of detail for this quiz result you may actually want to use a partial. In which case the syntax would be:
<%= render :partial => 'quiz/results', :locals => { :quiz => #quiz } %>
If it's relatively simple and you think it should be in a helper you should make simply make quiz a parameter. Requiring views to provide a specific instance variable to use your helper would likely be frowned upon by other developers.
def quiz_result(quiz) # no need to call it "current" when we supply quiz
# do some stuff
end
It also looks to me that you may want to restructure your models in some way. As you can see I implemented my examples with a Quiz class. I'm not sure what your data model looks like but when you are calling properties that are nested that deep it's generally a sign that something is wrong.
I haven't seen a good argument presented for either case, and stumbled onto this question when I was searching for an answer. Personally, I've been using the instance variables in helper methods where it's possible as this is the dryest approach across both helper and view. Rather than passing the instance variable from my view and defining my helper method to accept it, I can just use it directly in the helper. Slightly less typing, anyway...

Rails - Abstract/Shared Views

I have a few objects that are incredibly similar. In fact, they use STI and store all of the data in the same table in the DB. Currently, every time I add a feature or fix a bug, I have to update it in 3 different places, so I would like to DRY my code a little bit. The most common code duplication is in the views. I have seen people using render :template => 'shared/something' to render their common views, the problem is, I have many of these, but only for one particular object (Shipments), so I would prefer something like render :template => 'shipments/shared/something' or render :template => 'abstract_shipments/something'. More importantly though, I would like any of the actual classes to be able to override the template if it needs to.
Do y'all have any suggestions on how to go about this? Thanks very much for any answers!
The inherit_views plugin might do the job: http://github.com/ianwhite/inherit_views/tree/master. I've used it successfully before.
You could then have a common base controller class for all controllers needing to render shipments and have any common templates in that base controller's views folder with any specific overrides in the individual controllers' views folders.
Classes (models at least) don't - or shouldn't - know anything about how they're displayed. That's fundamental to the separation of concerns provided by the MVC pattern.
I think I'd try something like this:
Use a single controller for all the STI models, ShipmentsController or something similar. Apart from it seeming a logical thing to do, all your views will end up in the same place, which ought to help.
As for views, how about a "main" one for the common parts, and one partial for each set of subclass-specific fields? Use a case in your view, or probably more cleanly, a helper method, something like
def partial_name_for_subclass(obj)
if obj.is_a?(CrudeOilShipment) # or kind_of? or test the type column or use respond_to?
'crude_oil_shipment'
# etc
end
end
DRYer, consider using a convention (Rails loves conventions):
<%= render :partial => #shipment.class.name.downcase, :locals => { :item => #shipment } %>
where you have a partial for each subclass.
Hope some of that helps/makes sense...

Adding a photo to a model's has_many model from a view

I have a model, Thing, that has a has_many with ThingPhoto, using Paperclip to manage everything. On the "show" view for Thing, I want to have a file upload, and have it relate to the Thing model.
For some reason, I'm totally glitching on how this should be done. I've tried doing this (Haml):
- form_for #thing.thing_photos, :html => {:multipart => true} do |f|
= f.file_field :photo
= f.submit
... and I get this error:
undefined method `array_path' for #<ActionView::Base:0x24d42b4>
Google is failing me. I'm sure this is super easy, but I just can't get my brain around it.
Edit: I should have mentioned that if I change the #thing.thing_photos to just #thing, it works fine, in that it displays the form, but of course it's not associated with the correct model.
try #thing.thing_photos.new
this should instance a new model of thing_photos. If you're not using nested routes you'll have to add the thing_id as a hidden field.
If you're not tied to Paperclip (and if you're just doing images), I've had a lot of luck with fleximage. It's got really good documentation for image upload, manipulation, and display (with all sorts of neat effects like watermarking and stuff).
It doesn't quite answer your question, but it might be a cool thing to look into.
But I bet your problem related to the fact you're creating a form for an array of objects (through an association). I bet something is finding the type of the object (should be "thing_photo" but is "array" in your example), and appending "path" to it to write the form post URL. Perhaps you should add a .first or create a partial?
The form should be for #thing_photo and should either pass in the thing_id or if it's a nested route you can get it from that
then in your thing_photos controller
#thing_photo = Photo.new(params[:thing_photo])
#if using nested route
#thing = Thing.find(params[:thing_id])
#if using hidden field
#thing = Thing.find(params[:thing_photo][:thing_id])
if #thing_photo.save
#this is what ties the thing photo to the thing
#thing.thing_photos << #thing_photo
end
If I understand the question correctly, this might help.

Can Rails Routing Helpers (i.e. mymodel_path(model)) be Used in Models?

Say I have a Rails Model called Thing. Thing has a url attribute that can optionally be set to a URL somewhere on the Internet. In view code, I need logic that does the following:
<% if thing.url.blank? %>
<%= link_to('Text', thing_path(thing)) %>
<% else %>
<%= link_to('Text', thing.url) %>
<% end %>
This conditional logic in the view is ugly. Of course, I could build a helper function, which would change the view to this:
<%= thing_link('Text', thing) %>
That solves the verbosity problem, but I would really prefer having the functionality in the model itself. In which case, the view code would be:
<%= link_to('Text', thing.link) %>
This, obviously, would require a link method on the model. Here's what it would need to contain:
def link
(self.url.blank?) ? thing_path(self) : self.url
end
To the point of the question, thing_path() is an undefined method inside Model code. I'm assuming it's possible to "pull in" some helper methods into the model, but how? And is there a real reason that routing only operates at the controller and view layers of the app? I can think of lots of cases where model code may need to deal with URLs (integrating with external systems, etc).
In Rails 3 and higher:
Rails.application.routes.url_helpers
e.g.
Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")
I've found the answer regarding how to do this myself. Inside the model code, just put:
For Rails <= 2:
include ActionController::UrlWriter
For Rails 3:
include Rails.application.routes.url_helpers
This magically makes thing_path(self) return the URL for the current thing, or other_model_path(self.association_to_other_model) return some other URL.
You may also find the following approach cleaner than including every method:
class Thing
delegate :url_helpers, to: 'Rails.application.routes'
def url
url_helpers.thing_path(self)
end
end
Any logic having to do with what is displayed in the view should be delegated to a helper method, as methods in the model are strictly for handling data.
Here is what you could do:
# In the helper...
def link_to_thing(text, thing)
(thing.url?) ? link_to(text, thing_path(thing)) : link_to(text, thing.url)
end
# In the view...
<%= link_to_thing("text", #thing) %>
I really like following clean solution.
class Router
include Rails.application.routes.url_helpers
def self.default_url_options
ActionMailer::Base.default_url_options
end
end
router = Router.new
router.posts_url # http://localhost:3000/posts
router.posts_path # /posts
It's from http://hawkins.io/2012/03/generating_urls_whenever_and_wherever_you_want/
While there might be a way I would tend to keep that kind of logic out of the Model. I agree that you shouldn't put that in the view (keep it skinny) but unless the model is returning a url as a piece of data to the controller, the routing stuff should be in the controller.
(Edit: Forget my previous babble...)
Ok, there might be situations where you would go either to the model or to some other url... But I don't really think this belongs in the model, the view (or maybe the model) sounds more apropriate.
About the routes, as far as I know the routes is for the actions in controllers (wich usually "magically" uses a view), not directly to views. The controller should handle all requests, the view should present the results and the model should handle the data and serve it to the view or controller. I've heard a lot of people here talking about routes to models (to the point I'm allmost starting to beleave it), but as I understand it: routes goes to controllers. Of course a lot of controllers are controllers for one model and is often called <modelname>sController (e.g. "UsersController" is the controller of the model "User").
If you find yourself writing nasty amounts of logic in a view, try to move the logic somewhere more appropriate; request and internal communication logic probably belongs in the controller, data related logic may be placed in the model (but not display logic, which includes link tags etc.) and logic that is purely display related would be placed in a helper.

Resources