Rails Model method that builds a string of links - ruby-on-rails

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!

Related

In Rails 5, how access cookies method in a model or helper?

How does one access cookies inside a Rails model or helper?
Trying to dry up some controller methods, I am trying to move multiple controller calls to cookies() into application_helper.rb and/or a model.
What doesn't work in application_helper.rb:
cookies[:foo]
ActionDispatch::Cookies.cookies[:foo]
ActionController.cookies[:foo]
ActionDispatch::Cookies::ChainedCookieJars.cookie[:foo]
All of which result in undefined method 'cookies'
Note: Well-meaning answers that simply regurgitate MVC dogma are mis-placed here... I've architected long enough (decades) to know when coloring outside the MVC lines (if possible) is the better route. But the precise syntax eludes me even after digging through Rails source. This question is prompted by a somewhat complex situation related to, among other things, inconsistent browser handling of cookies in cross-domain, ajax environment that sometimes includes local files (for which Chrome declines to manage cookies).
It's not a good idea :) Models are classes, and they shouldn't be aware of what is happening on a web level, and that's why cookies method is implemented in ActionController, but there's no such implementation in ActionModel or ActionHelper. If you need a cookie value in a model, pass a value from a controller then. This is how it should be done.
As #Vasili mentioned, cookies is only available in controllers. But if you want to access cookies in helpers or models, just pass it as an argument, for example:
helper example:
module ApplicationHelper
def some_helper(given_cookies)
given_cookies[:foo] = 'bar'
end
end
In view:
<%= some_helper(cookies) %>

Using a Decorator, (rails) Could not infer a decorator for ActiveRecord::Base

I'm having trouble using a decorator. I've never used one before and I've been trying to use one with regards to something that I've been doing for breaking up some emails.
However because I've never used one before, I've been having trouble even doing very simple things with my decorator and I'm thinking there is some form of setup issue with it. I do know that everything outside of my little feature (aka the gemfile and such) are all up to date and proper.
The error I am getting is simply,
Could not infer a decorator for ActiveRecord::Base.
Now I have a controller that is almost empty, but inside it, I have the active record portion saved like so.
class Admin::ReceivedEmailsController < Admin::ApplicationController
With my view being titled,
_receive_email.html.haml
All I am doing in my view as of right now is so:
%td= received_email.decorate
My decorator
class Admin::ReceivedEmailsDecorator < Admin::ApplicationDecorator
def received_email
if can? :update, #customer
received_email.content
else
"You need to have the correct admin access to view the email"
end
end
I feel like this would have to be such an elementary thing for me to be missing, but I'm not sure what it is. Would anybody have any idea what I'm missing?
After much further research, reverse engineering further decorators and reading more documentation. I learned that a model or a helper is needed for a decorator to be properly used. Which due to my partial I did not have one specific model or helper to use.

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.

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.

View helper link_to in Model class

Using Rails 3, is there a way to use link_to helper, or any helper for that matter, inside model?
There are some reasons that you may need link_to in a model. Yes, #andy, it's a violation of MVC, but that doesn't mean you should get points for not answering the question.
#schwabsauce, it's easier than that. The first line isn't even strictly necessary if you do it in an initializer or something. Same thing works for .sanitize and .raw and a whole load of other awesome functions.
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
ActionController::Base.helpers.link_to(whatever)
If you want to use autopaths you may have to do this inside your link_to:
Rails.application.routes.url_helpers.page_path(#page)
Be very careful following the advice outlined in Chuck's post if you're doing this in Rails 3.2.1 . It would seem as though that approach is not a safe way to go about including the link_to helper in non-view classes in Rails 3.2.1. There is a safer way (that works for us in any case) outlined below.
When we used the approach in Chuck's post in one of our classes, it ended up having very troubling and difficult to debug consequences. It ended up causing side effects / bugs that only turned up in very specific (and rare) circumstances.
The problem, as far as we can tell, is that this line:
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
Is telling ActionView::Base to include the Rails.application.routes.url_helpers, which ActionView::Base apparently already does on its own. Having it include the url_helpers a second time, seems to cause re-initialization of the routes state (#_routes in classes that have included the ActionDispatch::Routing::UrlFor module).
This leads to seemingly random and unexplained "undefined method 'url_for' for nil:NilClass" exceptions in views that attempt to call, directly or indirectly, the url_for method after ActionView::Base has included the url_helpers the second time.
The solution that worked for us was instead of telling ActionView::Base to include the url_helpers again, just include the UrlHelper module yourself wherever you might need it.
Then when you need to use link_to and have access to the path you can simply do this (assuming login_path is valid for your app):
include ActionView::Helpers::UrlHelper
...
link = link_to('here', Rails.application.routes.url_helpers.login_path)
It took us a very long time and quite a lot of head scratching to track down the bugs caused by the double-include and I just wanted to warn others to be careful when tweaking the behavior of the Rails base classes.
I got this to work with the following inclusions:
include ActionView::Helpers::UrlHelper
include ActionController::UrlFor
include Rails.application.routes.url_helpers
cattr_accessor :controller
def controller; self.class.controller; end
def request; controller.request; end
Then in my controller I populated the attribute (creating a controller from scratch requires a significant amount of data in the arguments hash).
Lead.controller = self
link_to helper, MVC violation
What Andy said,
if you're generating HTML in the model you probably need to take a big long look at what you're doing and why.
URL helpers
URLs on the other hand often come in handy outside of view-controller code, in all kinds of service/form/api/... classes for example, even in models if you have to.
Yes, Rails.application.routes.url_helpers is a module, but that doesn't mean you should just include it wherever or funny stuff will start happening as Gary put it:
https://www.destroyallsoftware.com/blog/2011/one-base-class-to-rule-them-all
What you can do is:
delegate :url_helpers, :to => 'Rails.application.routes'
and then use, for example
url_helpers.home_url
If you want to use any module stuff anywhere and don't mind going against all Ruby norms, throw this hackery into a service:
module View
extend self
class HelperIncluder
include ActionView::Helpers::UrlHelper
end
def link_to(*args)
HelperIncluder.new.link_to(*args)
end
end
And now:
View.link_to('a', 'b') => "a"
Not without hackery.
If you think you need link_to in a model, you're likely violating some principle of the Model-View-Controller architecture.
A model should be a place for data and business logic, but generating links is almost certainly a job for the controller or view (or, Rails specifically, in a helper class).

Resources