Custom variable in Rails view filename - ruby-on-rails

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.

Related

Rails Route Helper Methods included into Service Object not working properly

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

Rails 4 + append_view_path

I want to use dynamic path in my controller in rails gem.
I've added to
module MyGem
class FooController < Config.controller
before_action ->{ append_view_path "app/views/my_gem/#{wizard_name}" }
...
and in views I need to specify path like
app/views/my_gem/#{wizard_name}/my_gem/foo/some.erb
is in Rails some way, to cut the relative path of gem namespace, and get lookup path like?
app/views/my_gem/#{wizard_name}/some.erb
# or
foo/bar/some.erb
Thank you!
UPD:
I understand, that there is way with disabling isolate_namespace in Engine, but I believe, that it is not best and only option.
UPD2: For Rails4 this idea was very useful https://coderwall.com/p/p_yelg/render-view-outside-of-a-controller-in-rails-4
The Devise gem has a way of adding views lookup path by overriding the _prefixes method:
class DeviseController < Devise.parent_controller.constantize
include Devise::Controllers::ScopedViews
# Override prefixes to consider the scoped view.
# Notice we need to check for the request due to a bug in
# Action Controller tests that forces _prefixes to be
# loaded before even having a request object.
#
# This method should be public as it is is in ActionPack
# itself. Changing its visibility may break other gems.
def _prefixes #:nodoc:
#_prefixes ||= if self.class.scoped_views? && request && devise_mapping
["#{devise_mapping.scoped_path}/#{controller_name}"] + super
else
super
end
end
end
Can this be applied to your use case?
See source:
https://github.com/plataformatec/devise/blob/master/app/controllers/devise_controller.rb
Usually, you should only override the partial views or the functions from that gem, do not load from the gem lib like this, because when deploying to the real server it will raise many troubles for you to debug and improve.
You could make your how render method that reads an arbitrary file, interpret the ERB and render it as an HTML page for instance.
ERB.new(File.read(Rails.root.join('foo', bar', 'some.erb'))).result(binding)
By passing binding, the template will have access to the all the variables in the current context.
See ERB docs for more details: http://apidock.com/ruby/ERB
Assuming your gem is an engine, you should be able to simply call render :some in the engine. If the app has a view called <gem_name>/<controller_name>/some.html.erb it will be used.
Also, you can provide a version of that view in your gem that will be used if the app does not yet provide one.
If you need the wizard_name to also be looked up, I think the best way to do that would be to move that portion of the view path to the to the end where you are calling render.
So in your gem's controller you would write render "#{wizard_name}/some" and it would look for that view both in our app's app/views/<gem_name>/<controller_name>/<wizard_name>/some.html.erb and in your gem's app/views/<controller_name>/<wizard_name>/some.html.erb.

Ruby on Rails folder structure with helpers

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€. :)

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

Rails: macro style functions

In models and controllers, we often use Rails macros like before_validation, skip_before_filter on top of the class definition.
How is this implemented? How do I add custom ones?
Thanks!
They're just standard Ruby functions. Ruby's flexible approach to syntax makes it look better than it is. You can create your own simply by writing your method as a normal Ruby function and doing one of the following:
putting it somewhere that's accessible by your controllers such as application.rb
putting it in a file and requiring it in.
mixing the code into a class via the Ruby include keyword.
That last option is great for model classes and the first option is really only for controllers.
An Example
An example of the first approach is shown below. In this example we add code into the ApplicationController class (in application.rb) and use it in the other controllers.
class BusinessEntitiesController < ApplicationController
nested_within :Glossary
private
# Standard controller code here ....
The nested_within provides helper functions and variables to help identify the id of the "parent" resource. In effect it parses the URL on the fly and is accessible by every one of our controllers. For example when a request comes into the controller, it is automatically parsed and the class attribute #parent_resource is set to the result of a Rails find. A side effect is that a "Not Found" response is sent back if the parent resource doesn't exist. That saves us from typing boiler plate code in every nested resource.
That all sounds pretty clever but it is just a standard Ruby function at heart ...
def self.nested_within(resource)
#
# Add a filter to the about-to-be-created method find_parent_id
#
before_filter :find_parent_id
#
# Work out what the names of things
#
resource_name = "#{resource.to_s.tableize.singularize}"
resource_id = "#{resource_name}_id"
resource_path = "#{resource.to_s.tableize}_path"
#
# Get a reference to the find method in the model layer
#
finder = instance_eval("#{resource}.method :find_#{resource_name}")
#
# Create a new method which gets executed by the before_filter above
#
define_method(:find_parent_id) do
#parent_resource = finder.call(params[resource_id])
head :status => :not_found, :location => resource_path
unless #parent_resource
end
end
The nested_within function is defined in ApplicationController (controllers/application.rb) and therefore gets pulled in automatically.
Note that nested_within gets executed inside the body of the controller class. This adds the method find_parent_id to the controller.
Summary
A combination of Ruby's flexible syntax and Rail's convention-over-configuration makes this all look more powerful (or weirder) than it actually is.
Next time you find a cool method, just stick a breakpoint in front of it and trace through it. Ahh Open Source!
Let me know if I can help further or if you want some pointers on how that nested_within code works.
Chris
Chris's answer is right. But here's where you want to throw your code to write your own:
The easiest way to add Controller methods like that is to define it in ApplicationController:
class ApplicationController < ActionController::Base
...
def self.acts_as_awesome
do_awesome_things
end
end
Then you can access it from individual controllers like so:
class AwesomeController < ApplicationController
acts_as_awesome
end
For models, you want to reopen ActiveRecord::Base:
module ActiveRecord
class Base
def self.acts_as_super_awesome
do_more_awesome_stuff
end
end
end
I personally would put that in a file in config/initializers so that it gets loaded once, and so that I know where to look for it always.
Then you can access it in models like so:
class MySuperAwesomeModel < ActiveRecord::Base
acts_as_super_awesome
end

Resources