Identical Files behave differently due to link with controller - ruby-on-rails

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.

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.

Rails controller action duplication

I have a controller show action which does some stuff and renders a view but due to some custom routing, I need a totally different action in a totally different controller to perform the same stuff and render the same view.
I don't really wish to duplicate the code. Is there somewhere I can put it and call it from both locations?
Edit:
I basically need to run the following from Collection#Show AND also from Gallery#SplitUrl:
#collection = Collection.find_by_id(params[:id])
if #collection.has_children?
#collections = #collection.children
else
redirect_to [#collection, :albums]
end
I cannot just redirect_to Collection#Show at the end of Gallery#SplitUrl as the redirect causes my custom URL's to be lost as it's a new request.
You could put the view content into a partial (/app/views/home/_example.html.erb) and then use the render "example" command in the HomeController.
The "some stuff" code you talk about could be put into a helper file /app/helpers/... to save you from duplicating code in the two separate controllers although it could depend on what the code is trying to do in the first place.
http://guides.rubyonrails.org/layouts_and_rendering.html
This might provide more information on the subject in general.
I think the simplest approach would be to add a route definition for your new url and map that to your existing controller's action.
Something like follows:
# config/routes.rb
# Existing resource
resources :foos
# The resource in question
resources :bars do
get :show, controller: 'foos', action: 'show', as: bar_foo_common_show
end
Note that this will give you /bar/:id, where id represents a bar resource. So in your foo#show action, your finder needs to be executed on appropriate class. This is when a few lines of hacky codes get added, for e.g. checking for request referer.
I think moving the common code to a function in possibly application_controller and calling that function from both show action would be cleaner approach, but based on my understanding you already have a similar scenario except for the common code in application_controller, and would like to try out a different approach!

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 beginner config/routes.rb issue

I am having a little trouble understanding what config/routes actually routes to. For example lets say I started a brand new project and generated a Users controller and edited my config/routes.rb to look like this:
config/routes.rb
SampleApp::Application.routes.draw do
match '/signup', to: 'users#new'
end
I start the server and as expected, it says I don't have a "new" action in my Users controller. I create one:
users_controller.rb
class UsersController < ApplicationController
def new
end
I refresh the page and as expected it tells me I need a users/new template. So my question is: do my view templates always have to be the same as the controller and action names in "(controller name)/(action name)" format (in this case users/new.html.erb)? Is it not possible to name my template something random (e.g. users/rubyonrailsmeetup.html.erb instead of users/new.html.erb) if have a controller action linked to one of the site's functions?
Also, does adding "resources :users" to config/routes.rb by default match the view template file names with the behavior I mentioned above so that views must be named after their "controller/action" names?
Sorry for the bother, I'm just trying to figure out what's part of Rails' magic and what isn't.
Rails attempts to render the template with the same name as the action by default, if no other render or redirect is invoked in the controller action. Basically, there's an implicit render :action at the end of every controller action.
But you can override this easily enough by adding an explicit render, e.g.,
render :rubyonrailsmeetup
Edit for clarity: this call to render goes in the controller code, not in config/routes
do my view templates always have to be the same as the controller and action names in "(controller name)/(action name)" format
These are defaults, you can render any view from the action by giving render :view_file_rel_path at the end of the action
does adding "resources :users" to config/routes.rb by default match the view template file names
Anything added in routes.rb is directly related upto controllers only, i.e. it matches the request and maps it to the controller/action. Logic of view comes only inside the action code

Resources