Rails view and controllers, (if/else statements) - ruby-on-rails

I've just started my own rails project from scratch. I'm trying the view to display hello if the minutes variable is set to a certain value.
The code is in the controller right now, and I want it to display the output in the view. is this possible? or do I write it in the view? Not sure of doing it right.
home_controller.rb
class HomeController < ApplicationController
def index
#minutes = 23
#minutes = params[:minutes]
end
end
index.html.erb
<% if #minutes == 23 %>
<%= "helllo" %>
<% else %>
Anonymous
<% end %>

#minutes = params[:minutes] || 23

It's good enought in this simple case put some logic in the view but usually is better to add helper methods (Read the Getting Started guide is a good idea).
Probably you've already seen the app/helpers directory, in these helper files you can define methods which are available in the view layer, methods related to view layer but do stuff that would be weird or dirty to put in templates files.
For example in your case you could have a /app/helpers/time_helper.rb:
module TimeHelper
# I know the method name sucks a little
def show_hello_if_minutes_is_23(minutes = #minutes)
if minutes==23
"Hello"
else
"Anonymous"
end
end
end
and then use in your index.html.erb template:
<%= show_hello_if_minutes_is_23 %>
As you can see:
You can read the method name and understand what it does (at high level)
Logic is put in a ruby method
The method take a minutes argument but it's optional
And remember: usually repetition is evil (The DRY thing) but in view-land sometimes one time is too much (not in this simple case however).
UPDATE: I've just seen you put set the #minutes variable to 23 and then you overwrite it making the previous assignment useless. I don't know what you're trying to do in your controller but if your question is about having a default value for the minutes variable go with the Yuri's answer and use use the ||= operator: #minutes ||= params[:minutes].

Related

Rails Controller Confusion

this is my first post.
I'm brand new to Rails and I'm attempting to learn how to use it. To be clear, I have a brushing familiarity with Ruby. I'm pretty sure I get the MVC structure, but I'm having trouble understanding certain behaviors I'm experiencing.
Just in case anyone learned from the same source, I'm watching Derek Banas explain it. He explains the thing I'm having trouble with around 16:20. https://www.youtube.com/watch?v=GY7Ps8fqGdc
On to specifics- So I placed this line in my routes.rb file:
match':controller(/:action(/:id))', :via => :get
and I created an instance variable in the controller using this:
def sample
#controller_message = "Hello From The Controller"
end
And in a sample view I created, I call on the "controller_message" variable like this:
<%= "#{#controller_message}" %>
And it works on that one view half the time. Now from what I understand, I should see "Hello From The Controller" anywhere that line of code is placed in a view, right? Maybe I just don't understand how this functions, but I made other view files in the same directory in an attempt to see how controllers pass data to views. They load and everything, but I'm not getting the message from the controller. Sometimes, seemingly inconsistently, the controller message won't even display on the first view where it worked originally, especially if I navigate around the site a little. To get it to display that message again, I have to restart my server.
So am I just misunderstanding how MVC works, or is my software glitching (unlikely, I know), or what? I'm so confused.
I've heard so many great things about this community. Thanks in advance to anyone willing to help me. I'm so stressed out.
The #{} in <%= "#{#controller_message}" %>is string interpolation. The usual convention of displaying an instance variable in a view is simply <%= #controller_message %>
The variable #controller_message, declared in the sample method, makes that variable available to the view associated with that method. By default, rails will look for a corresponding view file that has the same name as the controller method, so in this case it will look for a view called sample.html.erb in the app/views/your_controllers_name folder
Well, to clarify things (because your question is a little vague): rails does a lot of stuffs behind the scene, like relating controller's name with view's name. That is why, in most case, you don't invoke a view to be render in controller's method (Rails does that to you based on file's name). So, would make sense the variables declared in a controller method be displayed only in the view with the same name as the controller method called. If you want to display a variable in a view that is not related with the controller method, you should invoke that view with methods like Render and Redirect, and pass the variables as arguments to those methods.
I.g:
other.controller
def edit
render "/another_view_folder/example.html.erb", #variable_to_be_displayed
end
Another thing:
<%= #controller_message %>
This is enough to display the variable. The way you were doing was Interpolation (use to concatenate variable with strings).
I hope I can help you!
Rails will implicitly render a view file if it is found in the view path.
So given:
# config/routes.rb
get 'foos/bar'
# app/controllers/foo_controller.rb
class FoosController < ApplicationController
def bar
#controller_message = 'Hello'
end
end
Rails will attempt to find the file bar.html.erb in app/views/foos when we request /foos/bar. If we want to render another view we need to tell rails to render it explicitly.
class FoosController < ApplicationController
def bar
#controller_message = 'Hello'
render 'some_other_view' # renders app/views/foos/some_other_view.html.erb
# or
render 'some_other_folder/some_other_view' # renders app/views/some_other_folder/some_other_view.html.erb
end
end
Now from what I understand, I should see "Hello From The Controller" anywhere that line of code is placed in a view, right?
No. Lets say you add another controller and view.
# config/routes.rb
get 'people', to: 'people#index'
# app/controllers/people_controller.rb
class PeopleController < ApplicationController
def index
end
end
# app/views/people/index.html.erb
<h1>The message is: <%= #controller_message %></h1>
It will just render The message is:. Since PeopleController does not set the instance variable #controller_message. Ruby will not raise an error if you reference an unassigned instance variable.
The way to reason about this is that the controller in Rails packages all its instance variables and passes them to the view context so that we can use them in the view.
After the controller has finished rendering it sends the rendered template and the program exits*. Instance variables, local variables etc, do not carry over to the next request.

Ruby on Rails: User helper method to read attribute

I'm trying to use a helper method to determine the value of an attribute for several records. Here is the basic function I am trying to get working (from the view):
<% if Baseline.where(subject_id: sub.subject_id).first.crf_status(crf) == 1 %>
<td bgcolor="#98FB98" >
<% else %>
My helper function is crf_status(crf), and it looks like this:
application_helper.rb
def crf_status(crf)
case crf
when Baseline then 'baseline_status'
when FollowUp3Week then 'follow_up_3_week'
...
end
end
So a working example would be if crf_status(Baseline) would return:
<% if Baseline.where(subject_id: sub.subject_id).first.baseline_status == 1 %>
<td bgcolor="#98FB98" >
<% else %>
Right now, the error is 'undefined method 'crf_status' for Baseline'. So based on what I've read, perhaps I have to reference ApplicationHelper in each controller? That doesn't sound right. Please let me know what you think.
Thanks.
edit. I forgot to make this more clear: crf_status(crf) is being passed an object from an array [Baseline, FollowUp3Week...].
The actual line starts with it as well -> if crf.where(subject_id:...
When you do method chaining like .first.crf_status(crf) you don't get a fresh global scope every time. I.e. to get this example to work your crf_status would need to be defined as an instance method on the Baseline model.
From a MVC design perspective, it's frowned upon to do database queries (i.e. where) from your views; you should do it from the controller instead. The choice to use helpers here is totally optional. By putting it in a helper all you're doing is making it inaccessible from code outside your views.
To cut to the chase, here's what you should write in your Baseline model file:
def crf_status(crf)
case crf
when Baseline then baseline_status
when FollowUp3Week then follow_up_3_week
end
end
Note that the baseline_status and follow_up_3_week are actually method calls with the implicit receiver self.
You are calling "crf_status" on an instance of a model, helpers can only be called on views and controllers.
You have to do something like this
<% if crf.where(subject_id: sub.subject_id).first.send(crf_status(crf)) == 1 %>
<td bgcolor="#98FB98" >
<% else %>
Anyway, that looks like a weird code smell (making queries on view is not right and that crf_status looks like something that you should move inside your models)
If you want to return a method that is to be called in the context, use the .send method.
Baseline.where(subject_id: sub.subject_id).first.send(crf_status(crf))
Whatever is returned from your method will be executed. This is a great metaprogramming example. You want to test against the class of the instance, so use the .class method on your case line. You'll want to return symbols not strings though, so do this:
def crf_status(crf)
case crf
when Baseline then :baseline_status
when FollowUp3Week then :follow_up_3_week
else :default
end
end
Edit: Changed case for type comparison

Rails 4: What Goes in Controller and Model?

I have been reading and watching few videos on learning Rails 4. All tutorials has their own code so, in my views, easy to follow. I can't seem to learn anything or remember few things so I have decided to use my own code and see if I could follow instead of using their code.
So far I understand the Controller corresponds with the views:
# In my controller
def index
#x = "I love Ruby"
end
And in my views (index.html.erb)
<% = #x %> #=> I love Ruby
That simple thing would work for the index page. Now what if want to refer other method calls in that view's index, how to do that? In the controller:
def index
#x = "I love Ruby"
end
Still within the controller's class:
def languages_i_hate
languages = %w[
Perl
PHP
C#
C++ ]
end
And in my index.html.erb:
<%= These are the languages I hate to bits: #{languages_i_hate.upcase}!
I got undeclared method or variable "languages_i_hate"
How do I call method names in a webpage?
What you are trying to do here is access a controller method in the view. When you do this the controller method is accessed as if it was a helper method. Normally controller methods aren't available to be used in this way, but you can tell the controller to make them available as helpers.
See http://apidock.com/rails/ActionController/Helpers/ClassMethods/helper_method
BTW, when you have methods in the controller which aren't actions, ie don't correspond to a route/url, you should put them in a protected section, by convention at the bottom of the controller. This makes it clear to rails and to the reader that they're not actions.
def index
#x = "I love Ruby"
languages_i_hate
end
def languages_i_hate
#languages = %w[Perl PHP C# C++ ]
end
index.html.erb:
<%= "These are the languages I hate to bits: #{#languages_i_hate}" %>
According to rails convention you have to make use of Helpers. Other approach is by use of locals while rendering template.
def index
#x = "I love Ruby"
render :template => "index.html.erb", :locals =>{:languages_i_hate => languages_i_hate}
end
def languages_i_hate
languages = %w[
Perl
PHP
C#
C++ ]
end
And in my index.html.erb:
<%= These are the languages I hate to bits: #{languages_i_hate.upcase}!%>

Can't suppress output in nested block helper in rails 3

This one is sort of twisting my noodle.
I have something resembling this (in a rails 3 engine if that matters)
class Builder
def initialize
#foos = []
end
def foo(&block)
#foos << helper.capture(&block) #helper being a class that is including ActionView::Helpers
end
def to_html
#foos.join "\n"
end
end
module ApplicationHelper
def widget
b = Builder.new
yield b
b.to_html
end
end
#in a view somewhere
<%= widget do |b| %>
<% b.foo do %>
static content
<% end %>
<% end %>
Everything is working out great, but that nested static content is getting output twice -- once where I want it, and once where widget was called.
From what I have read, capture is supposed to deal with this exact problem. I am pretty sure the problem stems from how I am calling capture (from a dummy proxy class that includes ActionView::Helpers), but the problem is that b.foo call is calling a method on a class instance, not from the context of something that will be mixed into the template.
Is there any way to get around this problem? Or am I approaching this from the wrong direction. I am trying to model something fairly involved and am really happy with the api, just can't seem to get passed this problem.
If you modify the helper method to pass in self, which would be the current view instance, and then use this to capture, you might not have this issue. Substitute your use of helper for the provided view instance.

What is the best way to avoid code duplication between views and controllers in Ruby on Rails?

I currently have code in my ApplicationController to check if a user is logged in and has the required access to perform a given action (the tests take place in a before_filter).
I require the same functionality in the views to decide if I should be showing the admin links in a list view, but how do I best avoid duplicating code in the controllers and views?
The way I have chosen to do it now, is to have the user_can_edit_customers? essentially be a wrapper for 'can_edit_customers?' on my User class:
ApplicationController:
class ApplicationController
And then do something similar in my view helpers.
This way all functionality is encapsulated in the User model but I still need to define wrappers in my controllers and helpers, but are there smarter ways of doing this?
Note, the user stuff is only an example - this also applies to other pieces of functionality.
I would say do away with the wrappers and just call can_edit_customers? directly on the user object passed to the view.
If you want to keep them a solution might be to use helper_method in your controller.
helper_method :current_user, :can_edit_customers?
def current_user
#current_user ||= User.find_by_id(session[:user])
end
def can_edit_customers?
#current_user.can_edit_customers?
end
This way the method also becomes available in the view.
<% if can_edit_customers? -%>...<% end -%>
Just to be more direct. The helper_method "macro" in a controller causes a controller method to behave as if it's also a method in the application helper.
helper_method :current_user, :can_edit_customers?
Personally I think you should not use helpers.
I would take a different solution.
Let's say that we have Cucumber, a model, that should not be editable by some users. I create #editable? method as follows:
class Cucumber < ActiveRecord::Base
def editable?(current_user)
# Something happens here.
end
end
Note that if a page is accessible by everyone, current_user might be false.
Then, in the views you're able to do:
<%- if #cucumber.editable?(current_user) -%>
<%# Something happens here. -%>
<%- end -%>
And, in the controllers, use a filter.
The best thing about this approach is that it follows Fat Model, and enables you to easily cover your permissions with unit tests.
I think helpers are the way to do what you want. As for checking in views whether the user has or not the priviledge to do something, you could put a flag in your session data (e.g. session[:admin] = true and check that in your view.
It is common practice to make methods like logged_in? available in the controller and the views. For most cases, you don't need to push down authorization logic into the models.
Definetly go with with the approach user Hates described. Have a look at plugins like restful_authentication and acts_as_authenticated, to see how they did it.
There are several railscasts (http://railscasts.com/episodes?search=authentication) covering this topic. For instance, you could write a helper that takes a block and is then used like this:
<%- admin_user_ do %>
<%= link_to .. %>
<%= link_to .. %>
<%= link_to .. %>
<%- end %>

Resources