link_to 'new' action in a different controller? - ruby-on-rails

I want to have a link at the bottom of my show.html.erb that links to the new action in a different controller.
class Sample < ActiveRecord::Base
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :samples
end
So, at the bottom of the show action for songs, I want to link to new for samples. This seems pretty easy, but I'm struggling to figure this out. I would also like to pass the id from the song to the form as :song_id

Fiddy, because you're new, let me explain how this works...
Routes
Your problem is that you don't understand the Rails routing structure - I'll hopefully explain it for you.
Rails, since it's an MVC framework, builds a series of "routes" for you. These "routes" are stored in the file available at config/routes.rb.
Routes, as described by the Rails documentation are as follows:
The Rails router recognizes URLs and dispatches them to a controller's
action. It can also generate paths and URLs, avoiding the need to
hardcode strings in your views.
The most important thing you should consider here is the way the routes generate paths for you. These paths are simply Rails "helper" methods, which you can call from your views. The reason these exist is two-fold -
They provide you with a DRY (don't repeat yourself) way of accessing / manipulating data
They are constructed around objects, helping maintain the object-orientated nature of Rails
These will likely mean nothing to you. However, what you need to realize that if set up your routes correctly, it seriously helps your app's infrastructure immensely.
--
Rails
This leads us quite nicely onto appreciating the way in which Rails works
Rails is an MVC (model view controller) framework. This might seem somewhat trivial, but in reality, it's one of the most important aspects to learn about Rails development, and here's why:
The Rails software system works by taking "requests" (user input) and then routing them to specific controller#actions. Those controllers then build model data from the database, and will translate that into either variables or objects, which you can use in your view.
The reason I mention this is that this type of development takes a lot of getting used-to, in that your program's flow is not about logic / functionality, but the accessibility of data. Therefore, when you ask about the routes or other parts of your app, you need to firstly remember what data you wish to show, and also how you want that data to be shown - this will give you the ability to construct & use the routes / controller actions which will get it to work properly
--
Fix
In terms of what you're saying, the way you'd go about achieving the result you want will be to use a nested route:
#config/routes.rb
resources :songs do
resources :samples #-> domain.com/songs/:song_id/samples/new
end
This will create a new route for you (which you can check by firing rake routes in your rails c (console). This will give you a path to use for your samples#new action:
#app/views/songs/show.html.erb
<%= link_to #song.name, new_song_sample_path(#song) %>
The above link will take you to the samples#show action, which you'll be able to populate with as much data as you require from the samples controller. The important thing to note is this action will have params[:song_id] available for you to either build an object from, or otherwise

<%= link_to "New Sample", new_sample_path(:song_id => #song_id) %>
Where #song_id is the variable that has that id in it.

Set paths in link_to tag which you can get by running rake_routes in terminal.
Ex
link_to "New song", new_sample_path(#song)
In the example given above #song is the instance variable of your current page.
You can also get some idea from here:
link_to Base URL Randomly Changed

Song Model:
accepts_nested_attributes_for :sample, allow_destroy: true
Route:
resources :songs do
resources :samples
end
Song's Show file:
<%= link_to "New Sample", new_song_sample_path(#song) %>
in url it will be:
/songs/:song_id/sample/new
Try this and let me know it works or not... I hope this helps you

Related

Rails 4 new form defaults from params

I am using form_for in the _form.html.erb view in order to create my form for both the edit and new actions, as per a standard scaffold.
I have a model Owner which has_many pets.
I would like to put an html link on my views/owners/show.html.erb to create a new pet for said owner. This link will point to the new action of pets_controller.rb which when accessed will render the view in pets/new.html.erb
What I want to happen is for the the owner_id to be passed with the link in the url to the new action of pets_controller.rb and then be used as the default for a collection_select in pets/new.html.erb
So I have a link to create a new pet but because that link was on a specific owner page, I want the form to create a new pet to have that owner already set, so the user does not have to select from the list.
This has to be done without changing the behaviour of the edit action/view in pets.
I know that I can pass GET arguments then access them in the controller via params, then create variables in the action which are passed to the view. I can then manually check for a default and set it in the view. I do not need assistance in coding if this is the only solution.
Is there is a better way to do this? A format with which I can pass the params such that the view will just pick them up? Without manually editing my controllers and views?
While my personal inclination would be to do as you said and pass a parameter in the link helper and then access the params array in the pets view, you may find that this is the perfect opportunity to explore Nested Resources. Essentially, you could declare owners/:owner_id/pets/:pet_id route with:
resources :owners do
resources :pets
end
You could then link to this route, and reference :owner_id without having to append the query string to the URI (making somewhat cleaner for reuse).
This is likely more work for you, but also potentially more extensible (and certainly more inline with the Rails way of doing things).
REVISION
Added the following regarding link helpers to the comments, but wanted to reflect it in the answer as well.
To show a pet should be:
<%= link_to owner_pet_path( owner_variable, pet_variable) %>
To view pets' index index should be:
<%= link_to owner_pet_path( owner_variable ) %>
The answer given to this question is fantastic.
As #ConnorCMcKee suggests it would be wise to consider nesting your routes. However, if you are a beginner as myself I found that it helped my learning to simply nest my second controller into the first (i.e. nest PetsController into OwnersController) as a first step. Then afterwards I would continue with the routes.
The method would be something like:
1./ In owners/index.html.erb:
Links to PetsController index action
The key to make this work is to send the :owner_id in your link parameters. Then that Pets index action will have access to that :owner_id and know which :owner_id called it.
2./ In PetsController you would then be able to find that Owner using that id, like so:
params[:owner_id]
Then your actions can start to take advantage of knowing what Owner called them. Remember though that all your redirects inside your PetsController need to preserve params[:owner_id]. That is because once you are inside that nested structure you have to maintain it and stay inside it and always know which :owner_id you are working with.

Rails named route with dynamic segments URL generation from model

Ruby on Rails 4.2+ only, please!
I've been looking all over for tips on how to make URLs pretty in Rails, and I'm struggling to see a solution I like.
What I want:
Hypothetical example: given Topic, Course, etc. models that have a bunch of fields (including URL-friendly slugs), I want to be able to
# ../routes.rb
# Match urls of the form /edu/material-engineering. These are read-only
# public URLs, not resources.
get 'edu/:slug', to: 'education#topic', as: :learn_topic
get 'edu/course/:id/slug', to: 'education#course', as: :learn_course
...
# I also have admin-only resource-oriented controllers for managing
# the content, but that's separate.
namespace :admin do
resource :topic
resource :course
...
end
# ../some_view.html.erb
# Generate URLS like this:
<%= link_to topic.name, learn_topic_path(topic) %>
<%= link_to course.name, learn_course_path(course) %>
What I don't want:
Messing with to_param. That's a dirty hack and completely breaks separation of concerns.
Resource/RESTful routing. There are no CRUD operations here other than "read."
link_to 'text', course_path(id: course.id, slug: course.slug). This completely defeats the purpose of not requiring views to know what params are required to generate a URL for a course.
EDIT: I know FriendlyId exists, but I'm precisely trying to understand how this sort of thing can be done and what the mechanics are, so that's not a solution for now.
There has to be a way to tell the named route helper topic_path(topic) to take the required parameters in the route (e.g, :slug, :id, whatever else) from the topic model object.
Anybody know? Thanks!
The best I've been able to come up with: just override the *_path helpers with my own implementation.
If you know a way to make the default helpers work, please chime in!
This problem boils down to one issue: the auto-generated *_path and *_url helpers don't give me the flexibility I want. What I want them to do is trivial, so without another option, I can just write my own:
module ApplicationHelper
def learn_topic_path(topic)
"/edu/#{topic.slug}"
end
...
end
Writing a few _path/_url helper overrides avoids all kinds of complication, and allows you to keep out of to_param, avoid including new plugins, etc.
One could probably go another step forward and generate the static components of the route from known routing rules, as well as infer what attributes one needed to extract from a model if the dynamic segment names line up to the model attribute names, but that starts to break down once you do more complicated things or add multiple models (e.g., 'edu/:topic_slug/:course_slug').
The big downside to doing this is that you now have to update routes in two places every time you change them: the route definition itself in routes.rb as well as the corresponding route helper in application_helper.rb. I can live with that for now.
You can use FriendlyId gem to achieve that.
Here's the link:
https://github.com/norman/friendly_id/blob/master/README.md
Let me know if you have questions.

What is the "_path" method in ruby on rails?

I'm learning RoR, and I'm getting very confused by the "_path" method as it's used in Controllers and Routes. To be more specific, I'm referring to the many different calls that take the syntax "(something)_path". So far as I know, they all seem to either encode or manipulate a URL or link. I'm having a hard time mastering the use of this type of method because I can't figure out what it's core functionality is supposed to be.
For example, I could use the following code to redirect an old URL structure to a page of listed Tweet instances in my config/routes.rb file:
get '/all' => 'tweets#index', as: 'all_tweets'
Only now can I use the following in an .erb file. Notice the "_path" code at the end of the line.
<%= link_to "All Tweets", all_tweets_path %>
I could also use the following code to create a link to an edit page (and another action) in a different .erb file:
<p><%= link_to tweet.user.name, edit_tweet_path(#tweet) %></p>
I've tried reading through my study materials as well as the RoR documentation, but I always end up more lost than when I began. Does anybody know the low-level definition of this "_path" method?
Helper
It's called a route helper, which means that Rails will generate them to help provide you with resource-based routing structures. I'll explain more in a second
--
To explain properly - Rails is just a framework.
Like all software, it is a series of files loaded in a particular order. As such, Rails creates a series of helper methods in the booting process. These "helper" methods can then be used throughout your application to call functionality / information as you require:
The Rails framework provides a large number of helpers for working
with assets, dates, forms, numbers and model objects, to name a few.
These helpers are available to all templates by default.
In addition to using the standard template helpers provided, creating
custom helpers to extract complicated logic or reusable functionality
is strongly encouraged. By default, each controller will include all
helpers. These helpers are only accessible on the controller through
.helpers
The route helpers (which are generated from your config/routes.rb file give you the ability to call routes which are resourceful. These might seem strange to begin with, but once you understand them, will help you inexorably.
--
Resourceful
To give you more clarity - Rails routes are known as resourceful
This means they are constructed around resources. To give you a brief definition of this, you need to appreciate that the resources of your application are the pools of data you can add to, and pull
from.
To explain further, because Rails is object orientated. If you're new, this won't mean very much, but keep it in mind, as when you progress through the language / work, you'll begin to see why this is important.
Object orientated programming puts OBJECTS at the center of the flow. Typically, you'd put logic at the center, but with OOP, it's the objects. This is very important for us, as it means that everything you do in Rails is based around the objects you can create.
As per the MVC principle (which, again, is what Rails is built on), you'll create / invoke your objects from your Models:
This means that if you want to create a series of routes to "CRUD" (Create Read Update Destroy) your objects, Rails is able to create the routes necessary to do that. This is where the resources directives come from inside the routes file:
Hope this helps!
Actually, these paths are generated based on your routes.rb. If you run this command at your project, you would be able to see all available on your app
rake routes
For example, if I declare my resources in routes.rb like this
resources :posts
then I would automatically have following available paths
posts_path
post_path
new_post_path
edit_post_path
If you use some strange abc_path which has not been declared in routes.rb, then you will get errors.
Hope this is helpful, you will definitely need to work more with Rails and then eventually you will understand all of these things :)
you could find definition for these methods in rails repository:
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb#L127

Creating links using link_to for hierarchical model

Okay, so I am new to Rails and am creating a project management system with the framework. For a couple of my models, views, and controllers I used scaffolding and had no problems. For other parts, I coded all of the parts myself.
So as an overview of my project, at the root of it all you can have many projects. Within all of these projects you can create multiple to-do lists. Within each to-do list, you can have multiple tasks. This is what I meant by "hierarchical" in the title.
I just created my lists page, and when I go to the URL directly in my browser (ex: http://localhost:3000/projects/3/lists/20/tasks/1) the task is displayed correctly. However, I do not know how to format my link that is in one of my to-do list views (the tasks are usually shown below the to-do list but now I want them to show on their own view).
Here is the code I currently have:
<%= link_to "#{task.description}", project_list_tasks_url(#list.id,task.id) %>
I know that the link_to "#{task.description}" is correct because I tried using it with a static URL (Google or something), but project_list_tasks_url(#list.id,task.id) is where I'm having trouble.
Can anybody help me out? I can provide as much code as you'd like from my to-do list or task controllers and views.
A couple of tips to help make routing less confusing. It can be a bit unnerving to get used to.
Routing Rule #1
Always check the output of rake routes to be sure how to call your various routing methods. You might think you know how your routes will play out by looking at routes.rb but you won't know until you look at the compiled routes table.
In your case you're expecting a route with the format:
/projects/:project_id/lists/:list_id/tasks/:id
Be sure that's the case. If it is, your call should look like:
project_list_task_path(#project, #list, task)
Note that the arguments here are :project_id, :list_id and :id, so all three are required in this case. Any in brackets in the path specification can be ignored, like :format usually is.
Routing Rule #2
Use the _path methods unless you strictly require the full URL. They're shorter and the output is easier to read and debug. They also don't inadvertently flip the URL in the browser and cause session problems if you don't properly differentiate between www.mysite.com and site.com.
Routing Rule #3
Don't forget there's a huge difference between #project and #project.id when it's supplied to a routing path method.
The router will always call the to_param method if it's available and this can be over-ridden in your model to produce pretty or friendly URLs. id is for your database and your database alone. to_param is for routing but you shouldn't be calling it manually unless you're doing something exceptionally irregular.
You generally should not nest resources more than one level deep, but putting that aside, the link_to format should be:
link_to task.description, project_list_task_path(#project, #list, task)
i.e. project_link_tasks_url should be project_link_task_url, and you have to pass the #project as the first argument (I'm assuming that your project is named #project). I've switched _url to _path so you can just pass the objects themselves as arguments rather than their ids.
See the documentation on creating paths and URLs from objects for details.

How to use Rails named route helpers with parameters?

given this route
match 'posts/hello/:name/:title' => 'post#show', :as => :hello
what are the ways that I can call hello_path ?
if i call hello_path(#post), what does it try to do?
I was hoping that the :name and :title files will bind to the path automatically but it seems that rails only know how to get the :id out of the model object.
instead, it only works if I call it like
<%= link_to "link2", hello_url(:name=> #post.name, :title=>#post.title) %>
(lack of proper documentation is really killing me)
To answer your two questions:
At the command line, runrake routes to see what routes there are in
your app. It will show you all the ways you can use the named routes,
just add "_path" or "_url" to the name of the route which is shown on
the left.
Calling hello_path(#post) will generate a URL to the
show page for that hello instance.
The way you are calling it is the norm:
<%= link_to "link2", hello_url(:name=> #post.name, :title=>#post.title) %>
However, this may work too:
<%= link_to "link2", hello_url(#post.name, #post.title) %>
Here is some documentation (other than the Rails API) which should help.
http://guides.rubyonrails.org/routing.html
To answer your question of "what does hello_path try to do?"
hello_path knows how many parameters it's supposed to get. This is from counting the named parameters in config/routes. It will accept either a hash or a list of arguments. If you give it a hash, the keys must match the names of the URL parameters. If you give it a list of arguments, it'll just match them up by position - the first argument with the first named parameter.
Then, it will call to_param on each parameter individually before joining them all together (see code here, 4.0 branch).
If you pass in an object when it's expecting 2 or more params, it won't even get around to calling to_param on the object. That's when you get errors with no stack trace that say something like
No route matches {:controller=>"posts", :action=>"show", :id=>#<Post ...>}
Working with 1 named parameter
If you've only got one named parameter, things are pretty straightforward. If you need to look up your posts by name instead of id, you can just redefine to_param
class Post < ActiveRecord::Base
...
def to_param
name
end
end
Working with multiple named parameters
But if the URL has more than one named parameter in it, then redefining to_param isn't enough. Let's say you tried this:
# app/models/post.rb
class Post < ActiveRecord::Base
...
def to_param
{name: name, title: title}
end
end
# app/views/posts/index.html.erb
<%= post_path(post) %>
In this case, you'll get a routing error because you're not passing in enough arguments to post_path (see above). To get around this, I just call to_param explicitly:
# app/views/posts/index.html.erb
<%= post_path(post.to_param) %>
This is a little less slick than most Rails routing magic, but works perfectly well. If you later change the way you're looking up Posts, all you have to do is redefine to_param. No need to worry about all the places you've called post_path
Under the hood
The relevant code to look at is actionpack/lib/action_dispatch/routing
The other answers (at the time of this writing) are fine, but your reply to GregT's answer shows a lack of understand about Rails, which is fine -- we've all been there.
Specifically, three of the key principles behind Rails: convention over configuration, the model-view-controller architecture (MVC), and REST. It's stuff at the beginning of every beginning Rails book. Beginners often think they can just jump to the first chapter with code, but Rails differs from many other topics in that the first chapters explain important concepts and aren't just intro chapter filler. Because Rails isn't "code", it's a "framework of code".
"Convention over configuration" means if you follow certain conventions then you benefit from behaviors baked into Rails. Routing is one of those areas, if not the biggest, where convention benefits the developer although it is entirely configurable.
Paths following a specific routing format, are parsed into the controller, action and possibly an id, format, and additional parameters. By default and at minimum, a Rails (and also a Sinatra) path takes the following format and order:
/controller_name/action_name
It's a little more complicated than that, with more options, and it looks more like this in actuality:
/controller_name/action_name/(id)(.format)(?param=value)(&...)
...but it's more detail than is necessary for this answer.
The controller is the C in MVC, or the class that handles the request. The action is one of the seven RESTful actions (index, show, new, create, edit, update, and destroy) within that controller. Not all actions require an id (index, new and create) and not all of them are get requests (requests that generate a view, for instance destroy, create and update don't have views).
Putting it all together we see this example:
/articles/edit/1
...will route the request to the 'edit' action in the ArticlesController controller passing along id 1. It's expected that the controller will do some housekeeping, like authentication and authorization, then retrieve Article model (MCV) with ID 1 and pass it along to the "edit" view (MCV).
It's best, especially for new Rails developers, to stick to these conventions and perhaps add a few additional actions beyond those provided by REST.
You can step outside this convention by configuring in your routes.rb file an alternative routing scheme, not following REST, etc., but you'll be like a salmon swimming upstream -- it's a lot harder than going with the flow. If you go down that path (pun) you'll make a lot of additional work for yourself, probably reinvent the wheel somewhat, and lose advantages provided by the Rails framework. If you find yourself in that situation for whatever reason, perhaps Rails isn't the right tool for your job.
You can also call it like this
hello_path(#post.name, #post.title)
Hope it helps.

Resources