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
Related
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
I have to make a rails API only i.e. input is POST request and output will be an JSON response. I have to interact with mysql database with my own table names i.e. mysql tables are already created.
Below is the folder structure with "helpers" even though we are not using any "views". We are accessing the helper methods from our controllers. Please confirm if I am correct or not. Thanks in advance.
1) app/controllers/application_controller.rb
class ApplicationController < ActionController::API
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
2) app/controllers/feature_management_controller.rb
class FeatureManagementController < ApplicationController
def populate_bean
#json = OrionCountryList.new.sample_function
end
def send_response(bean)
helper = FeatureManagementHelper.new
if (bean.method.eql?"get_feature_list") && (!bean.app_key.blank?) && (bean.app_key!=nil) && (bean.app_key.casecmp("NULL")!=0)
logger.info bean.print_bean "Request for fetching featureList by app_key : " + bean.app_key.to_s + " And userID: " + bean.user_id.to_s
##json_response = helper.get_feature_list bean
else
logger.error "METHOD NOT FOUND. method during feature management :"+bean.method+" app_key :"+bean.app_key
##json_response = {:message => "API not avaliable"}
end
logger.info("Final json_response sent to app : "+##json_response.to_json)
render :json => ##json_response
end
end
3) app/helpers/application_helper.rb
class ApplicationHelper
APP_CONFIG = YAML.load(File.read(File.expand_path('../../../config/app_config.yml', __FILE__)))
end
4) app/helpers/feature/feature_management_helper.rb
class FeatureManagementHelper
def get_feature_list(bean)
response = Hash.new
response = {:success_code => "1"}
return response
end
end
Here we are using "class" key word inside the helpers. But on searching, it seems "module" key word is needed. But we couldn't find the way to access module methods of helpers inside controllers.
Any help is appreciated.Thanks!!!
UPDATE
#Ekkerhard, Thanks for the suggestion,
I have refrained from using helpers in the above way mentioned and instead used PORO for implementing my business logic as suggested by #spikermann using
this_link
Upon implementing the changes, my code structure looks something like this:
1) app/controllers/feature_management_controller/feature_management.rb
class FeatureManagementController
class FeatureManagement
def get_feature_list(bean)
response = Hash.new
response = {:success_code => "1"}
return response
end
end
end
Similarly for any controller "test_controller" I have a folder named "test_controller" at the location /app/controllers/
and I am keeping the business logic inside a test.rb file inside this "test_controller" folder.
2) We have all the controllers inside the /app/controllers
3) We have all the models inside the /app/models
4) We are reading the configuration file inside /config/application.rb
class Application < Rails::Application
config.autoload_paths += Dir["#{config.root}/lib/**/"]
APP_CONFIG = YAML.load(File.read(File.expand_path('app_config.yml', __FILE__)))
config.time_zone = "New Delhi"
config.active_record.default_timezone = :local
config.autoload_paths += Dir["#{config.root}/app/**/"]
end
Though if I read the config file from the feature_management.rb file things are working just fine i.e. adding this line to the feature_management.rb file :
/app/controllers/feature_management_controller/feature_management.rb
APP_CONFIG = YAML.load(File.read(File.expand_path('../../../../config/app_config.yml',
__FILE__)))
but upon trying to read the configuration from the application.rb file I am getting an error :
NameError (uninitialized constant FeatureManagementController::FeatureManagement::APP_CONFIG):
I was wondering whether this is the correct way to proceed and is there a better way to do it.
Appreciate your inputs..!!!
If you need, you can include the module inside your controller and access the methods. Like:
include HelperModuleName
Frankly, to me it seems that you are trying to just "get by" with Rails here and are using other paradigms in a Rails environment. I don't think you're going to be happy with that in the long (or even short) term; if you use Rails that way it will just get in your way.
First of all, I would not use helpers in this way, at all, ever. In my opinion, helpers are there solely for cutting out "stupid" code from view templates, to cut down on "programming" clutter inside otherwise HTML'ish/JSON'ish templates. And they are definitely never domain methods. They do domain-agnostic stuff like render form elements, complex tables or things like that.
Just because you are not outputting HTML but JSON does not mean you have to ditch the MVC paradigm completely, like you are doing. There are JSON templates, see In Rails, how do you render JSON using a view? .
You are using a helper to load configuration. Configuration lives in config/application.rb, config/environments/*.rb, config/initializers/*.rb etc. - see http://guides.rubyonrails.org/configuring.html . You should never need to load the YAML in a helper.
Your controller code suggests that you do not use routes.rb to structure your requests. Branching like you have in your controller is a big smell for me. See http://guides.rubyonrails.org/routing.html .
Maybe not the answers you are looking for, but that's my 2€. :)
I'm using Rabl to generate XML output in a rake task:
xml = Rabl.render #listings, 'feeds/listings', :format => :xml
# do stuff with xml
However, I need to use multiple helper methods in the rabl view file referenced, and I keep getting a NoMethodError as I expected from the answer to this question.
I tried using extends and include in the class used by the rake task but I still get the same error on the helper methods:
require "#{Rails.root}/app/helpers/feeds_helper.rb"
class SerializeData
extends FeedsHelper
def perform
xml = Rabl.render #listings, 'feeds/listings', :format => :xml
# do stuff with xml
end
end
My question is: is there any way to use helper methods in rabl view files generated in this way? (or at least in a way that I can still render them as a string in a rake task?) The helper methods are used many, many times to correctly format various data per fixed requirements, so it would be very difficult to remove them entirely.
I ended up with a monkey-patchy solution.
I noticed that the NoMethodFound error came from an instance of the Rabl::Engine class, so I included the needed routing and helper methods in that class and was then able to access them:
require "#{Rails.root}/app/helpers/feeds_helper.rb"
...
class Rabl::Engine
include Rails.application.routes.url_helpers
include FeedsHelper
end
Also note that the URL host needs to be set if using url in addition to path helpers (e.g. root_url and root_path):
Rails.application.routes.default_url_options[:host] = "www.example.com"
I would definitely prefer a non-monkey-patch solution or at least one where helpers could be included as needed depending on the controller of the action rendered. I'll wait to accept this to see if anyone can come up with such an answer.
You can pass in a scope object with the scope parameter. So if you have access to an object with the helper included, like when in the view context, then you can pass that
eg:
<%= Rabl::Renderer.json(object_to_render, 'api/v1/object/show', view_path: 'app/views', scope: self).html_safe%>
So outside of the view context you'd need to pass in a custom object with the helpers included to make this clean.
eg
class RakeScope
include FeedHelper
end
Rabl::Renderer.json(object_to_render, 'api/v1/object/show', view_path: 'app/views', scope: RakeScope.new() )
I've not tried the second option but the first works great.
While not quite the same problem, I had a similar problem accessing helpers from RSpec specs. I created a helper function that creates a scope that you can use to add whatever helpers you need. The following gave me access to the path and url helper methods and something similar should work for Rake.
#spec/support/rabl_helper.rb
def render_rabl(object, options={})
options = {
format: 'json',
view_path: 'app/views',
file: example.example_group.top_level_description,
scope: RablScope.new
}.merge(options)
result = Rabl.render(object, options.delete(:file), options)
options[:format] == 'json' ? JSON.parse(result) : result
end
class RablScope
include Rails.application.routes.url_helpers
end
In a Rails 3.2 app I need to access url_helpers in a lib file. I'm using
Rails.application.routes.url_helpers.model_url(model)
but I'm getting
ArgumentError (Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true):
I've found a few things written about this, but nothing that really explains how to solve this for multiple environments.
i.e. I assume I need to add something to my development.rb and production.rb files, but what?
Closest I've seen to an answer suggested using config.action_mailer.default_url_option, but this does not work outside of action mailer.
What is the correct way to set the host for multiple environments?
This is a problem that I keep running into and has bugged me for a while.
I know many will say it goes against the MVC architecture to access url_helpers in models and modules, but there are times—such as when interfacing with an external API—where it does make sense.
After much searching I've found an answer!
#lib/routing.rb
module Routing
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
included do
def default_url_options
ActionMailer::Base.default_url_options
end
end
end
#lib/url_generator.rb
class UrlGenerator
include Routing
end
I can now call the following in any model, module, class, console, etc
UrlGenerator.new.models_url
Result!
A slight improvement (at least for me) on Andy's lovely answer
module UrlHelpers
extend ActiveSupport::Concern
class Base
include Rails.application.routes.url_helpers
def default_url_options
ActionMailer::Base.default_url_options
end
end
def url_helpers
#url_helpers ||= UrlHelpers::Base.new
end
def self.method_missing method, *args, &block
#url_helpers ||= UrlHelpers::Base.new
if #url_helpers.respond_to?(method)
#url_helpers.send(method, *args, &block)
else
super method, *args, &block
end
end
end
and the way you use it is:
include UrlHelpers
url_helpers.posts_url # returns https://blabla.com/posts
or simply
UrlHelpers.posts_url # returns https://blabla.com/posts
Thank you Andy! +1
Use this string in any module controller to get application URL-helpers works in any view or controller.
include Rails.application.routes.url_helpers
Please note, some internal module url-helpers should be namespaced.
Example:
root application
routes.rb
Rails.application.routes.draw do
get 'action' => "contr#action", :as => 'welcome'
mount Eb::Core::Engine => "/" , :as => 'eb'
end
Url helpers in module Eb:
users_path
Add include Rails.application.routes.url_helpers in controller contr
So after that helper should be
eb.users_path
So inside Eb module you can use welcome_path same as in root application!
Not sure if this works in Rails 3.2, but in later versions setting default url options for the routes can be done directly on the routes instance.
So for example, to set the same options as for ActionMailer:
Rails.application.routes.default_url_options = ActionMailer::Base.default_url_options
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.