When I was developing my RoR skills with some basic tutorials I encountered a problem. What I am trying to achieve is having comments belonging to posts, with no separate index or individual view. This part was easy.
Here comes tough one. I want post_comment_url to return address with fragment identifier: http://example.com/posts/2#comment-4. It would allow me to use redirect_to in it's simplest form, without :anchor parameter (which would be against ruby way of keeping things simple).
How to do that?
Instead of altering Rails' default behavior, it'd probably be better to wrap up your needs in a helper method:
# in app/controllers/application_controller.rb
class ApplicationController
helper :comment_link
def comment_link(comment)
post_comment_url(comment.post, comment, :anchor => "comment-#{comment.id}")
end
end
The call to helper will allow you to access that method in your views as well as your controllers.
Related
I have a method on a model called Photo. I have it finding a selection of things from elsewhere in my app. All I need it to do at the end is to create a string of links that I can then output later on when the method is called on an instance.
My code is:
cars.map { |c| link_to(c.name, c) }.join(" AND ")
But i'm hitting this error:
undefined method `link_to' for #<Photo
Any ideas how to fix this?
link_to is a view helper which means it's only available in Rails views by default because it's a router / request concern.
If you specifically want to use link_to you have to include it or reference it directly.
See this SO answer
include ActionView::Helpers::UrlHelper
...
cars.map { |c| link_to(c.name, c) }.join(" AND ")
There are other ways of getting paths than using link_to that I would recommend you consider:
It's arguable that the Rails team would tell you to use UrlFor as the tip in that link suggests:
Tip: If you need to generate URLs from your models or some other place, then ActionController::UrlFor is what you're looking for. Read on for an introduction. In general, this module should not be included on its own, as it is usually included by url_helpers (as in Rails.application.routes.url_helpers).
UrlFor also allows one to access methods that have been auto-generated from named routes.
class User < ActiveRecord::Base
include Rails.application.routes.url_helpers
def base_uri
# named_route method that uses UrlFor
user_path(self)
end
end
User.find(1).base_uri # => "/users/1"
create your own concern to bring in route helpers via ActionMailer as this article suggests
As you may see if you scroll through other SO questions about including view helpers in models, there is pushback on using router and request -based methods outside of controllers and views because it violates the principles of MVC.
I think your use case can give you some peace of mind about this, but it's worth knowing the water is murky and some people may advise you otherwise.
The traditional Rails wisdom (and what I'm about to give you here) is that models should not be creating HTML. They also shouldn't have methods that return HTML. Creating HTML <a> tags should be done much closer to the user interface: in a view template or maybe in a view helper. One reason is that the particular way the hyperlink should be generated is a concern of the view. (Does it need a nofollow attribute? class attributes? This will change, even from one view to another.) And the model should not have any knowledge of these details.
When you do generate links in the views, then you have access to all the helpers such as link_to.
Instead, as I understand it, a model should be responsible for returning its own data. Maybe in your case that'd be an array of dicts of :label, :url. I.e., pure data that'd be easy to pass to link_to.
Hope that helps!
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.
Im using bootstrap & rails and have a user model and post model..users create posts (collections)..
with bootstrap in the navbar i want the user to be able to click a dropdown which displays the name's of their posts..i did this on one controller with a private method and a before_action but i don't want to do this for all the controllers and it didn't work for the application controller...
is there a better way to do this??
I was doing this
def list
#user = User.find_by_username(params[:id])
#collections = #user.collections
end
and a
before_action :list
at the top of the controller
What's the most semantic way to accomplish this??
If you could move both to your application controller, then it would be available to any controller. More generally, I'm not sure if this is the best approach to solve your problem.
These tips might also be useful.
Are you using devise? Or some other authentication plugin? If so you're likely going to have a current_user helper. This would allow you to simply do #collections = current_user.collections
To the extent possible, I recommend using more descriptive names for your actions and parameters. def fetch_list_collections might be a better name or instead of passing a param named id, perhaps your param should be named username. These naming conventions become extremely important both for others who might look at your code as well as for yourself if you return to it and are trying to remember what you wrote N months ago.
Your list action is generating a N+1 queries. Meaning that you're hitting the database multiple times when you should do so just once. See the rails guide on this. You might also look at ways to avoid this w/ devise. Devise is pretty well documented and I'll bet there is something in the wiki discussing this.
You may want to consider limiting when you call this action - at a minimum - a post request to an update action? What about before they've logged in? current_user might be nil and you'd have an error attempting to call a collections method on nil.
Take your time learning this stuff. You don't have to learn it all at once, but I thought the above might be helpful.
I got it to work with this in the application controller
before_action :list
private
def list
#collections = current_user.collections
end
thanks #arieljuod
I am currentlly trying to design a RESTful MembershipsController. The controller action update is used only for promoting, banning, approving,... members. To invoke the update action the URL must contain a Parameter called type with the appropriate value.
I am not too sure if that is really RESTful design. Should I rather introduce sepearate actions for promoting,... members?
class MembershipsController < ApplicationController
def update
#membership= Membership.find params[:id]
if Membership.aasm_events.keys.include?(params[:type].to_sym) #[:ban, :promote,...]
#membership.send("#{params[:type]}!")
render :partial => 'update_membership'
end
end
end
Neither. The only "actions" a controller should handle are GET, PUT, POST, DELETE (+other http verbs). I realize posting this on a question tagged with "rails" is heresy but today I don't care.
One RESTful way to do this is to create new "processing resources" for each of these operations and POST the member to that resource to invoke the action.
When I say create a new resource, you can interpret that to mean, create a new controller.
To me this is one of the cases when you just shouldn't pull your hair out in efforts to adhere to REST conventions. Your model doesn't seem to fit in with the traditional CRUD concept, and the RESTful principle of differentiating actions via HTTP verbs doesn't seem to belong here too.
If I were you, I would split that action into separate actions for what you need to do with your memberships (trying to stay as DRY as possible). That would make the controller code more readable. And by creating some routes I would also make the view code cleaner (promote_membership_path, etc.). But that's just me :), so see what fits most for you.
EDIT:
here is an article which explains my point of view a bit: http://www.therailsway.com/2009/6/22/taking-things-too-far-rest
Well, there is more than one way to do things. The questions you should be asking yourself, is how many states are there? How often do new states get added? Etc.
If there wouldn't be that many states, I would create separate actions + a before filter for the find, but I think this is more of a personal preference. If you really want to keep it short, you can put each method on one line. So:
class MembershipsController < ApplicationController
before_filter :find_membership
def ban; #membership.ban!; render :partial => 'update_membership' end
def promote; #membership.promote!; render :partial => 'update_membership' end
protected
def find_membership
#membership = Membership.find(params[:id)
end
end
To answer your question whether it is RESTful: yes, your update method is perfectly RESTful, but remember that a PUT should be idempotent. So if I execute the same method twice, is the result the same? i.e. what happens if I ban a user and ban him again?
My application is in RoR
I have an action/view called showsummary where the ID has been passed into the URL, and the controller has used that to instantiate #vendor where #vendor.name is the name of a company.
I would like the URL to be, rather than showsummary/1/ to have /vendor-name in the URL instead.
How do I do that?
All of these solutions use find_by_name, which would definitely require having an index on that column and require they are unique. A better solution that we have used, sacrificing a small amount of beauty, is to use prefix the vendor name with its ID. This means that you dont have to have an index on your name column and/or require uniqueness.
vendor.rb
def to_param
normalized_name = name.gsub(' ', '-').gsub(/[^a-zA-Z0-9\_\-\.]/, '')
"#{self.id}-#{normalized_name}"
end
So this would give you URLs like
/1-Acme
/19-Safeway
etc
Then in your show action you can still use
Vendor.find(params[:id])
as that method will implicitly call .to_i on its argument, and calling to_i on such a string will always return the numerical prefix and drop the remaining text- its all fluff at that point.
The above assumes you are using the default route of /:controller/:action/:id, which would make your URLs look like
/vendors/show/1-Acme
But if you want them to just look
/1-Acme
Then have a route like
map.show_vendor '/:id', :controller => 'vendors', :action => 'show'
This would imply that that it would pretty much swallow alot of URLs that you probably wouldnt want it too. Take warning.
I thought I'd mention String#parameterize, as a supplement to the tagged answer.
def to_param
"#{id}-#{name.parameterize}"
end
It'll filter out hyphenated characters, replace spaces with dashes etc.
Ryan Bates has a great screencast on this very subject.
Basically you overload the to_param method in the Vendor model.
def to_param
permalink
end
Then when you look up the resource in your controller you do something like this:
#vender = Vender.find_by_name(params[:id])
But the problem with this is that you'll have to make sure that the vendors' names are unique. If they can't be then do the other solution that Ryan suggests where he prepends the the id to the name and then parses the resulting uri to find the item id.
You do this by modifying the routes that are used to access those URL's and changing them to use :name, rather than :id. This will probably mean that you have to write the routes yourself rather than relying on resources.
For instance add this to the routes.rb file:
map.with_options :controller => "vendor" do |vendor|
vendor.connect "/vendor/:name", :action => "show"
# more routes here for update, delete, new, etc as required
end
The other change that will be required is that now you'll have to find the vendor object in the database by the name not the id, so:
#vendor = Vendor.find_by_name(params[:name])
Internally (at least to my knowledge/experimentation) whatever parameter name is not specified in the URL part of the route (i.e. not within the "/Controller/Action/:id" part of it) is tacked on to the end as a parameter.
Friendly ID
http://github.com/norman/friendly_id/blob/26b373414eba639a773e61ac595bb9c1424f6c0b/README.rdoc
I'd have to experiment a bit to get it right, but there's two primary parts to the solution.
1) Add a route.
in config/routes, add a line that sends requests of the form baseurl/controller/:vendor-name to the action showsummary, (or maybe a new action, show_summary_by_vendor_name)
[also, if you planned on using baseurl/:vendorname, that's fine too]
For convenience, make sure the parameter is something like :vendor-name, not the default :id
2) Write the controller action.
In the controller file, either edit your showsummary action to differentiate based on whether it's called with an id or with a vendorname, or just write a show_summary_by_vendor_name. (depending on best practices, and what route you wrote in 1. I don't know off the top of my head which is preferable)
You can then do
#vendor = Vendors.find_by_name(params[:vendor_name])
or something like that, and then render it the way you would in regular showsummary.
3) Use that as the link.
Once you confirm that baseurl[/controller?]/vendor-name works, and shows the summary, make sure all the links in your application, and elsewhere, use that link. Off the top of my head, I can't remember how difficult it is to integrate a custom route into link_to, but I think it's doable. Most search engines [google] rely heavily on links, so good SEO will have you using those named links, not the numbered ones. I think. I don't know much about SEO.
Take a look also at this quck start to SEO for Rails