Rails Controller Confusion - ruby-on-rails

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.

Related

RESTful API in rails

I am very new to rails and following a tutorial for RESTful API so let me excuse if it is of not very good quality as I am equally a starter for these kind of terminologies as well.
I created a controller kitten with a command rails g controller kitten index
and in the index method I posted this code -
class KittenController < ApplicationController
def index
require 'open-uri'
kittens = open('http://placekitten.com/')
response_status = kittens.status
response_body = kittens.read[559, 441]
puts response_status
puts response_body
end
end
and un commented match ':controller(/:action(/:id))(.:format)' in routes.rb
When i navigate through this - http://localhost:3000/kitten
this is what i am getting in my browser -
Kitten#index
Find me in app/views/kitten/index.html.erb
and this in my command line -->
Now my question why it so although i am expecting it in my browser but the cat is shown in command prompt instead of browser ..i am new to rest resource so please excuse if it is a bad one :(
I don't know what tutorial you're following, but doing this seems like a very odd thing to do for Rails in general and learning RESTful APIs in particular.
Anyway, the puts in your controller outputs text to Ruby's standard out, which is going to be the terminal where the server started. That's why this is appearing in the console rather than in your browser: puts is putting it there.
If you want this to appear in a web page, you'll need to make a view for that controller action. Perhaps following further along your tutorial will get you there: if not, you might want to find a better one.
You should read the Model-View-Controller rails guide.
Controllers provide the “glue” between models and views. In Rails, controllers are responsible for processing the incoming requests from the web browser, interrogating the models for data, and passing that data on to the views for presentation.
Define your variable in the controller and display it in the view:
class KittenController < ApplicationController
def index
#variable = 'Hello World'
end
end
In your view (app/views/kitten/index.html.erb):
<%= #variable %>
Rails controllers setup responses with a render call.
When the call is not performed it instantiates the appropriate view and renders that view. In your case that is index.html.erb
Try this:
render :text => kittens.read[559, 441], :status => kittens.status

Is there a more elegant way? (accessing a rails controller from a static home page?)

OK, this is working but I feel there is a better way to do this in Rails... I have a home page which, if you have not signed in, is not currently pulling in anything from any model or controller. It exists at /pages/home.html.erb
On that page, I want to grab the next party from my Parties model and tell the website visitor about that party. Easy enough, right?:
/app/controllers/parties_controller.rb
def nextparty
#party = Party.find(:first, :order => "begins_on")
end
Now, in my home page, I used this and it works fine:
/app/views/pages/home.html.erb
<% #PartyCont = PartiesController.new() %>
<% #party = #PartyCont.nextparty() %>
<h3>The next party is <%= #party.name %></h3>
I tried helper methods, partials, ApplicationHelper, but this was the only code that actually worked. Most of the other things I tried seemed to fail because the #Party class was not instantiated (typically the error indicated the class with a temporary name and "undefined method").
Hey, I'm happy that it works, but I feel like there is a better way in Rails. I've seen a few posts that use code like the above example and then say "But you really shouldn't ever need to do this!".
Is this just fine, or is there a more Rails-like way?
UPDATE:
I think the problem is more than just elegance... I just realized that all RSPEC tests that hit the home page are failing with:
Failure/Error: get 'home'
ActionView::Template::Error:
undefined method `begins_on' for nil:NilClass
Thanks!
You want a controller behind every view and you don't want views crossing controller boundaries in order to present information. Consider having a welcome controller (or whatever you prefer to call it). It can have an index action:
def index
#party = Party.find(:first, :order => "begins_on")
end
In config/routes.rb, make it the root controller action:
root :to => "welcome#index"
Also, to DRY that up add a .nextparty class method to the Party model and call that from both of your controller actions instead of the find method.
Your view should only show data that already was made available by your controller. You want to display a party resource, so the request should go to the parties controller. If I understand your use case correctly, than more specifically to the index method on the PartiesController.
There you should have the following code:
def index
#party = Party.find(:first, :order => "begins_on")
end
That instance method will be available in your corresponding view app/views/parties/index.html.erb
<h3>The next party is <%= #party.name %></h3>
To make this available as your homepage you will have to adjust your route as well:
config/routes.rb
root :to => "parties#index"
Your view should contain as little logic as possible and mainly be concerned with how things look.
Your controller should get data for the view ready and make sure to call the right method on the model.
All the heavy business logic should be in your model.
I think you should work through a basic introductory Rails tutorial.

Way to see which rails controller/model is serving the page?

This might be a slightly odd question, but I was wondering if anyone know a Rails shortcut/system variable or something which would allow me to keep track of which controller is serving a page and which model is called by that controller. Obviously I am building the app so I know, but I wanted to make a more general plugin that would able to get this data retroactively without manually going through it.
Is there any simple shortcut for this?
The controller and action are defined in params as params[:controller] and params[:action] but there is no placeholder for "the model" as a controller method may create many instances of models.
You may want to create some kind of helper method to assist if you want:
def request_controller
params[:controller]
end
def request_action
params[:action]
end
def request_model
#request_model
end
def request_model=(value)
#request_model = value
end
You would have to explicitly set the model when you load it when servicing a request:
#user = User.find(params[:id])
self.request_model = #user
There are a number of ways that I know of:
First you can do rake routes and check out the list of routes.
Second you could put <%= "#{controller_name}/#{action_name}" %> in your application.html.erb and look at the view to see what it says. if you put it at the extreme bottom you'll always get that information at the bottom of the page.
The controller can be accessed through the params hash: params[:controller]. There isn't really a way to get the model used by a controller though, because there is no necessary correlation between any controller and any model. If you have an instance of the model, you could check object.class to get the model's class name.

Identical Files behave differently due to link with controller

I am building my first app with ROR and stumbled upon a couple of problems due to my understanding of the MVC
I have a page to add a new item, and this works fine, rails magically hooks it up to the items controller and somehow by magic it knows to look in the method 'new' as the page is called new.
But this layer is confusing me, as i need to now create a different version of new, same functionality but with a different look so to use a different layout to application.html.erb
So i attempt to create a copy of new.html.erb and create bookmarklet.html.erb - both contain exactly the same code: a link to a form. but of course bookmarklet will error on me because it does not have that link in the controller - how do i 'wire' up bookmarklet so that i can call the new method and so that it can behave in a similar way to the identical new.html.erb
Also, how can i tell the new bookmarklet.html.erb to ignore the application.html.erb and get its layout from another file?
thanks in advance
The magic happens in the routes. Rails uses something called RESTful routes, which is taking HTTP verbs and assigning standard actions to it. the new action is a GET request in HTTP speak, and if you are using scaffolding or following REST, will have the ruby call to build a new object in the controller, as an instance variable so you can use it in your view.
You have to tell rails routes that you want to BREAK this arrangement and to let /items/bookmarklet be used in the controller.
In your routes.rb file replace resources :items with
resources items do
member do
get 'bookmarklet'
end
end
In your controller put:
def bookmarklet
#item = Item.new
render :template => "bookmarklet", :layout => "different_layout" # or you can put this on the top of the controller, which is my style, but whatevs.
end
You should look into partials, if you are doing this as they clean up your code immensely.
A better way to think of things is to start with the controller instead of the view html.erb files. So each public method in your controller is effectively a page or action in the site. When you need a new action or page, add the method to the controller first. Then create the relevant view files.
So in your controller you'll need something like:
def bookmarklet
#item = Item.new(params[:item])
#item.save
render :template => "items/bookmarklet.html.erb", :layout => "different_layout.html.erb"
end
Normally you don't need to call render manually in the controller, but since you want a different layout than the default you need to specify it using render.

Rails Sub-controllers?

I'm pretty new to Rails and have an issue which I can't quite get my
head around as to the architecturally 'correct' way of doing it.
Problem relates to what I kinda call sub-controllers. The scenario is
this:
I have a series of pages, on which is a panel of some form containing
some information (think the user panel on gitHub top right).
So, in my app, I have controllers that generate the data for the pages
and render out the responses which is fine, but when it comes to this
panel, it seems to me that you would want some sort of controller action
dedicated to generating this panel and it's view.
Question is, how do you go about doing this? How do I render a 'sub
controller' from within a view?
I would put the logic in a helper or a module. (http://api.rubyonrails.org/classes/ActionController/Helpers/ClassMethods.html)
Then render partials where you want these things displayed. (http://api.rubyonrails.org/classes/ActionView/Partials.html)
Like Herman said, if it's logic that you need generated after the controller hands off to the view (ie, the Pages controller generates a page view, but you want a customized panel) then put it in a helper. Or, call a separate method in your Pages controller before handing off to the view. Or, if it's a lot of logic, create a Module and stick it in your /lib folder. So you could have a whole Panel module with methods that generate different parts of your Panel and which are called by your controller. But if you want to call these methods from within the view, then you should use a helper instead.
I dont think a module is what is required here, modules are required for shared behaviour across a small subset of your classes.
What I think is required here is the understanding of the inheritance of ApplicationController and also layouts
so, for example, my layout might look like:
<html>
<head><title>Foo</title></head>
<body>
<%= render :partial => (current_user ? "/shared/user_widget_bar" : "/shared/login_bar") %>
<%= yield %>
</body>
</html>
Any code that i want to use for it would go in my ApplicationController since it would be shared across the majority of my app:
before_filter :generate_user_widget
def generate_user_widget
if current_user
#avatar = ...
#unread_messages = ...
end
end
I understand that it might be cleaner for it to belong in a separate controller BUT honestly, unless the code is huge, it doesn't matter and can even still be put inside a module which is then included by ActionController. However it does need to be inside ApplicationController if you consider the scope of it.
If there are more related pages, say for example, you have a Rails app that manages multiple sites and you want shared behaviour across a particular site, try creating a parent controller which has no actions and only private methods, any controllers that need to have access to those methods can inherit off it. That way you can apply before filters to all controllers which inherit off it, saving you the pain of forgetting to add one in your non-parent controllers.
e.g:
class SiteA::SiteAParentController < ApplicationController
before_filter :generate_user_widget
...
end
class SiteA::ProductController < SiteA::SiteAParentController
def index
...
end
end
well, if you really need to call a controller action from the view, you can use components. They were part of the framework, now they only exist as plugins. One such plugin that seems to be well maintained is here: http://github.com/cainlevy/components/tree/master
from its docs:
== Usage
Note that these examples are very simplistic and would be better implemented using Rails partials.
=== Generator
Running script/generator users details will create a UsersComponent with a "details" view. You might then flesh out
the templates like this:
class UsersComponent < Components::Base
def details(user_or_id)
#user = user_or_id.is_a?(User) ? user_or_id : User.find(user_or_id)
render
end
end
=== From ActionController
class UsersController < ApplicationController
def show
return :text => component("users/detail", params[:id])
end
end
=== From ActionView
<%= component "users/detail", #user %>

Resources