Rails Route Helper Methods included into Service Object not working properly - ruby-on-rails

I wanted to make my route helpers available in my service object.
Ex:
blog_path(blog) #make available in service object
The issue is that I am using passenger, so the application is relative to the domain.
Ex: Instead of the path loading: www.my_domain.com/blog/1, passenger loads the path with: www.my_domain.com/this_app/blog/1.
Currently my route helper in my service object is rendering the first version and not the second version.
Here is what my service object looks like:
class BuildLink
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
RouteHelpers = Rails.application.routes.url_helpers
attr_accessor :blog
def initialize(blog)
#blog = blog
end
def init
content_tag(:li, link_to(“Show Blog“, RouteHelpers.blog_path(blog)))
end
end
The route works locally because on localhost I do not have a relative path. But when I put it in production it does not work because passenger is expecting the application name as the relative path, but the service object is not including the application name within the url it generates.
That relative path works everywhere else in my application, it just doesn't properly generate the relative path within the service object.

The issue is that actionview-related methods are not available to POROs.
In order to get all the great stuff from actionview: you need to utilize the view_context keyword. Then: you can simply call upon actionview-related methods from your view_context:
class BuildLink
attr_accessor :blog, :view_context
def initialize(blog, view_context)
#blog = blog
#view_context = view_context
end
def init
content_tag(:li, link_to(“Show Blog“, view_context.blog_path(blog)))
end
end
So for example: from your controller you would call upon this PORO like so:
BuildLink.new(#blog, view_context).init
For more information, see below references:
Rails doc on view_context
Utilization of view_context via presenter pattern, shown in this article
Railscast which talks through utilizing view_context via presenter pattern

Related

Ruby on Rails: Reference to a singleton class action in routes

I am trying to work over a class using Ruby on Rails in order to create a simple controller. In that sense, I have a singleton and I need to refer routes to it. How is it possible?
The message I get:
The action 'foo' could not be found for Test::TestController
The controller file, inside a Test folder:
class Test::TestController < ApplicationController
class << self
def index
render json: {test:"Hello World!"}
end
def foo
render json: {response:"It works!"}
end
end
end
The routes file:
Rails.application.routes.draw do
namespace 'test' do
resources :test
end
get '/:id', to: 'test/test#foo'
end
Its not possible. And its not even a remotely good idea.
Rails controllers take their input in form of the request and env which encompasses things like server settings and anything the middleware has stuffed away as initializer arguments. They are not globals like in for example PHP.
The actions of a controller themselves don't actually take any arguments. So even if you could declare your actions as class methods you would have absolutely no context. And its actually pretty damn irrelevant since you would have to replace the entire router layer to even get it called.
Ruby does not even have real singleton classes either. If you want an object that can't be instantiated use a module (and no you can't make a module a controller in rails).

Add helper to rails controller instance only

I have some helpers that are defined on runtime that are specific for a single call, e.g. a single instance of a controller (the next call could have different helper methods). Is there a robust way to add a helper method to an instance of a controller and it's view only, without adding the helper to other instances and views of this controller?
To define a helper for ALL instances, you could use the .helper_method method, e.g.
class Article < ApplicationController
helper_method :my_helper
def my_helper
# do something
end
end
I digged around in the source code, and found the (fairly private looking) #_helpers method which returns a module that contains all helpers for this instance. I could now use some meta programming to define my methods on this module
def index
_helpers.define_singleton_method(:my_helper) do
# do something
end
end
But I don't like this approach because I'm using a clearly private intended method that could easily change in the future (see the leading _).
If I only needed the helper inside the controller instance only, I could just call #define_singleton_method on the instance directly, but this doesn't make it available to the view.
So I'm looking for an official "Rails way" to define a helper for a single instance of a controller and it's view, like Rails provides with it's class method .helper_method.
I'm not sure if there is an official Rails way of doing this.
You could create an anonymous module and extend from that. Since this solution uses pure Ruby, you'll have to extend both the controller and view.
before_action :set_helpers, only: :index
def index
# ...
end
private
def set_helpers
#helpers = Module.new do |mod|
define_method(:my_helper) do
# do something
end
end
extend(#helpers)
end
<% extend(#helpers) %>

Rails: Make Route Helper Methods Available to PORO

Within a Plain Old Ruby Object (PORO) in my rails app: I have the following method:
def some_method
content_tag(:li, link_to("Do something", somewhere_path(object.id)))
end
First: the object didn't understand the method content_tag, so I added the following which made the object understand that method:
include ActionView::Helpers::TagHelper
Then the object didn't understand link_to so I added the following which made the object understand that method:
include ActionView::Helpers::UrlHelper
Now, it doesn't understand my route: somewhere_path(object.id).
Question: How can I make the PORO in my rails app understand the helpers which generate routes?
Followup Question: Is there an easier way to include all of this functionality into my PORO object? Perhaps there is a way to only include one major module and get all of this functionality (as opposed to perhaps needing to require 3 different modules).
You either have to do what you describe in your self-answer (link to revision I refer to), or inject some context into your POROs. Where context is something which knows all those methods. Something like this:
class ProjectsController
def update
project = Project.find(params[:id])
presenter = Presenters::Project.new(project, context: view_context) # your PORO
# do something with presenter
end
end
And your PORO would look like this:
module Presenters
class Project
attr_reader :presentable, :context
def initialize(presentable, context:)
#presentable = presentable
#context = context
end
def special_link
context.somewhere_path(presentable)
end
end
end
Me, I like neither of them. But sometimes we have to choose a lesser evil.
If anyone happens to know of a current way to get access to all of these methods with one include statement then let me know.
Why, yes. There is a way.
module MyViewCompatibilityPack
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
def url_helpers
Rails.application.routes.url_helpers
end
end
class MyPoro
include MyViewCompatibilityPack
...
end
The issue is that actionview-related methods are not available to POROs.
In order to get all the great stuff from actionview: you need to utilize the view_context keyword. Then: you can simply call upon actionview-related methods from your view_context:
class BuildLink
attr_accessor :blog, :view_context
def initialize(blog, view_context)
#blog = blog
#view_context = view_context
end
def some_method
content_tag(:li, link_to(“Show Blog“, view_context.blog_path(blog)))
end
end
So for example: from your controller you would call upon this PORO like so:
BuildLink.new(#blog, view_context).some_method
For more information, see below references:
Rails doc on view_context
Utilization of view_context via presenter pattern, shown in this article
Railscast which talks through utilizing view_context via presenter pattern

Custom variable in Rails view filename

By default, Rails can find views with the format, locale and template language in the filename (so I can create index.de.json.erb)
Is it possible to add another, custom parameter to the view's filename?
I would like to pass the current subdomain, so http://foo.example.com/ would render index.foo.html.erb, and http://bar.example.com/ would render index.bar.html.erb (both of them with index.html.erb fallback).
The resolving pattern that is used to look up views can only contain variables that are registered with the ActionView::LookupContext class. The first step is therefore to register a new variable (subdomain) with the LookupContext class. You should do this in an initializer:
ActionView::LookupContext.register_detail(:subdomain) do
['default_subdomain']
end
Now the LookupContext knows about the subdomain, it can be included in the resolving pattern. For more detail about changing the resolving pattern, see the ActionView::FileSystemResolver documentation, but essentially you should include the following, also in an initializer:
ActionController::Base.view_paths = ActionView::FileSystemResolver.new(
Rails.root.join('app', 'views'),
':prefix/:action{.:locale,}{.:subdomain,}{.:formats,}{.:handlers,}'
)
This pattern is eventually passed to Dir.glob (after the :* variables have been replaced). The glob pattern {.:subdomain,} means “either .:subdomain or nothing”, which provides the fallback to a view file with no subdomain if the file with a subdomain isn't found.
The final step is to update your ApplicationController to pass the subdomain to the LookupContext:
class ApplicationController < ActionController::Base
def details_for_lookup
{:subdomain => [request.subdomain]}
end
end
(This answer was mostly figured out by reading source code, some of these features aren't documented. It was tested with Rails 3.2.5)
I don't think you need a custom handler here. You don't perform some prepossessing of your templates, but you just need your templates to become domain-specific.
I would consider making something like this:
# in ApplicationController:
before_filter :set_view_paths
def set_view_paths
self.class.view_paths = Rails.root.join('app', 'views', controller_name, request.subdomain)
end
Then you have to put your templates for the foo domain into foo folders of each views/controller_name path.
Also check append/prepend_view_path documentation to allow defaults in case of absence of a domain view.

Access url_helper from an Engine class

I try to access URL helper from inside a Module class. Here's my code :
module Station
class Plugins
##plugins = [] unless defined?(##plugins) && ##plugins.class == Array
class << self
def all
return ##plugins.sort_by { |p| p[:weight] }
end
def register(plugin = {})
raise "plugin must be a Hash (ie.: `register(:foo => 'bar')`)" unless plugin.class == Hash
raise "plugin must contain :name (ie.: `register(:name => 'my_plugin')`)" unless plugin[:name].present?
plugin[:weight] = 1 unless plugin[:weight].present?
plugin[:href] = eval("#{plugin[:name].downcase.pluralize}_url") unless plugin[:href].present?
##plugins.push(plugin) unless ##plugins.include?(plugin)
end
end
# include default plugins:
Station::Plugins.register(:name => "Pages", :weight => -1)
end
end
When I run my server, I got this error back:
undefined local variable or method `pages_url' for Station::Plugins:Class
I read a lot about "how to call url helper from a Class", but none of the solutions I found worked for me.
Firstly, what you're not making clear is if the url helper you're trying to access is from the parent application the engine is added to, or if it's from another engine this engine has included.
If from parent application, then all you need is:
main_app.pages_url
So you'll need to edit your code accordingly. Note that the "main_app" part is not the name of the parent application but literally the words "main_app".
If you're trying to access a url helper of an engine that you included in this engine, then you need to access it like you would to access any engine from the parent application. I.e.:
Your gemspec file should include:
s.add_dependency('my_engine', path: "~/path/to/my_engine")
routes.rb should include:
mount MyEngine::Engine => "/my_engine", as: "any_name_for_my_engine"
and then access it in your code using:
any_name_for_my_engine.pages_url
Hope this helps.
EDIT:
Change your engine's application.rb file to look as shown below, so that you can inherit all the parent application's ApplicationController variables and routes:
class Station::ApplicationController < ApplicationController
end
You might want to read the Rails Guide on Engines for a more detailed explanation on how to make these work together. Ask again if you're still having trouble.
What worked for me was to include the helpers into the specific class:
include ENGINE_NAME::Engine.routes.url_helpers
include Rails.application.routes.url_helpers
This worked for me, found it while reading ActionPack's code.
Contains all the mounted helpers across different engines and the main_app helper for the application. You can include this in your classes if you want to access routes for other engines.
include ActionDispatch::Routing::RouteSet::MountedHelpers

Resources