I have a Rails 4 mounted engine MyEngine
Inside a lib module I'm using the url_helper to find a route.
MyEngine::Engine.routes.url_helpers.send("test_controller_url",{:id => 1, :lang => I18n.locale})
And I have configured rspec with the default_url_options in spec/spec_helper.rb like this.
class ActionView::TestCase::TestController
def default_url_options(options={})
{ :params => { :lang => I18n.default_locale }, :host => "test.host" }.merge options
end
end
class ActionDispatch::Routing::RouteSet
def default_url_options(options={})
{ :params => { :lang => I18n.default_locale }, :host => "test.host" }.merge options
end
end
The resultant url I'm expecting from the call is:
http://test.host/controller/1/test?lang=en
But I'm getting the url:
http://test.host/?lang=en/controller/1/test?lang=en
I did some debugging into actionpack/action_dispatch/http/url.rb in actionpack-4.0.1 of rails.
The options that come to the url_for method are:
{
:params=>{:lang=>:en},
:host=>"test.host",
:use_route=>"my_engine",
:only_path=>true,
:lang => :en,
:path=>"/",
:script_name=>nil,
:user=>nil,
:password=>nil
}
and
{
:params=>{:lang=>:en},
:host=>"test.host",
:action=>"test",
:controller=>"my_engine/controller",
:use_route=>"test_controller",
:only_path=>false,
:id=>1,
:path=>"/controller/1/test",
:script_name=>"/?lang=en",
:user=>nil,
:password=>nil
}
And in the dummy rails application inside spec/dummy/config/routes.rb I've mounted the engine with:
mount MyEngine::Engine, :at => "/"
It seems to produce the malformed url only with rspec and not even with rails console
I also noticed that there are two calls being made to url_for, one for the engine and one for the actual path.
What am I doing wrong here? Is it a configuration thing or is there an actual bug?
Removing the :params and :lang from the default_url_options did the trick.
Related
I'm redirecting to https like so:
redirect_to :protocol => 'https://', :status => :moved_permanently
However, the parameters don't go through like this. I can pass specific parameters through like this:
redirect_to :protocol => 'https://', :status => :moved_permanently, :param1 => params[:param1], :param2 => params[:param2]
How would I make it so that it just passes through every parameter on the url instead of having to explicitly declare each parameter?
Figured it out:
redirect_to({:protocol => 'https://'}.merge(params), :flash => flash)
This will keep all URL params through the redirect.
With Rails 4.2 and above, passing the whole params hash will result in adding ?controller=foo&action=bar to the querystring. Instead, you should do this:
redirect_to protocol: 'https', params: request.query_parameters
If you only need this at the controller level, you can use:
MyController < ApplicationController
force_ssl
end
You can use :only or :except if you only need this on a certain action. See documentation:
http://api.rubyonrails.org/classes/ActionController/ForceSSL/ClassMethods.html
Alternatively, if you just want your whole app to use ssl (assuming rails 3.1 or greater):
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = true
end
end
You could just pass params as an argument like this:
redirect_to :protocol => 'http://', :status => :moved_permanently, :params => params
I'm using rails 3.0.5, rspec2 with latest capybara.
Routes setup like:
scope "(:locale)", :locale => /de|fr|it|en/ do
resources :dossiers
end
In application_controller I have this:
def default_url_options(options={})
options[:locale] = "es"
options
end
So in my views I can use
link_to 'test', dossier_path(1)
without any problems.
But when I do the same in capybara's visit it tries to use the 1 for the locale and not for the id. It only works when I use
visit dossier_path(nil, 1)
or
visit dossier_path(:id => 1)
But both are ugly and looks like a dirty hack. So why do I need to use this dirty hack and what do I jave to do, so that I can use the path methods just like in the views (so without the dirty hack of having to add nil or explicitly pass :id => ...)? :)
I ran into a similar issue. You can set the default_url_options in a before block like this in request specs:
before :each do
app.default_url_options = { :locale => :es }
end
Unfortunately the route generation happens outside of Application Controller. And Capybara doesn't do any magic to provide default url options from it to route helpers.
But you can specify default locale inside your routes.rb
scope "(:locale)", :locale => /de|fr|it|en/, :defaults => { :locale => "es" } do
resources :dossiers
end
And now if you don't pass :locale option to a route helper it will default to "es". Actually, it isn't necessary to keep def default_url_options anymore in your controller.
I'm running rails 3.2.6 and I use a technique that I found here https://github.com/rspec/rspec-rails/issues/255 under Phoet's comment. Just put this somewhere in /spec/support and it should cover all your specs
class ActionView::TestCase::TestController
def default_url_options(options={})
{ :locale => I18n.default_locale }
end
end
class ActionDispatch::Routing::RouteSet
def default_url_options(options={})
{ :locale => I18n.default_locale }
end
end
Opposite as shown here under Using Capybara with RSpec the only way I've been able to get it working is writing
visit user_path(:id => myuser.id.to_s)
so for you it should be
visit dossier_path(:id => "1")
Does it work?
I've seen similar questions on this, but not quite what I'm looking for...
Forgetting for a moment the wisdom of doing this, is it possible to do this?...
/object/update/123?o=section # ==> route to SectionController#update
/object/update/456?o=question # ==> route to QuestionController#update
...and if so, how would that be done?
Assuming you're using Rails 3+, you can use an "Advanced Constraint" (read more about them at http://guides.rubyonrails.org/routing.html#advanced-constraints).
Here's how to solve your example:
module SectionConstraint
extend self
def matches?(request)
request.query_parameters["o"] == "section"
end
end
module QuestionConstraint
extend self
def matches?(request)
request.query_parameters["o"] == "question"
end
end
Rails.application.routes.draw do
match "/object/update/:id" => "section#update", :constraints => SectionConstraint
match "/object/update/:id" => "question#update", :constraints => QuestionConstraint
end
More concise than #moonmaster9000's answer for routes.rb only:
match "/object/update/:id" => "section#update",
:constraints => lambda { |request| request.params[:o] == "section" }
match "/object/update/:id" => "question#update",
:constraints => lambda { |request| request.params[:o] == "question" }
Setting aside the question of whether it is wise to do so, the answer to "is this possible" is 'yes':
class QueryControllerApp
def self.call(env)
controller_name = env['QUERY_STRING'].split('=').last
controller = (controller_name.titleize.pluralize + "Controller").constantize
controller.action(:update).call(env)
rescue NameError
raise "#{controller_name} is an invalid parameter"
end
end
MyRailsApp::Application.routes.draw do
put 'posts/update/:id' => QueryControllerApp
end
Basically the route mapper can accept any Rack application as an endpoint. Our simple app parses the query string, builds the controller name and calls the ActionController method action (which is itself a Rack application). Not shown: how to deal with query strings with any format other than 'o=<controller_name>'
This question relates to this SO question and answer (rails-3-ssl-deprecation
) where its suggested to handle ssl in rails 3 using routes.rb and routes like:
resources :sessions, :constraints => { :protocol => "https" }
# Redirect /foos and anything starting with /foos/ to https.
match "foos(/*path)", :to => redirect { |_, request| "https://" + request.host_with_port + request.fullpath }
My problem is that links use relative paths(i think thats the correct term) and once I'm on a https page all the other links to other pages on the site then use https.
1) Whats the best way to get back to http for pages where https isn't required? Do I have to setup redirects for all them(I hope note) or is there a better way. Would the redirects be like this:
match "foos(/*path)", :to => redirect { |_, request| "http://" + request.host_with_port + request.fullpath }
2) If redirects back to http are required, how do I handle a case where I want all methods to be http except one? ie foos(/*path) would be for all foos methods. But say I wanted foos/upload_foos to use ssl. I know how to require it
scope :constraints => { :protocol => "https" } do
match 'upload_foos' => 'foos#upload_foos', :via => :post, :as => :upload_foos
end
but if I put in the http redirect to the foos path what happens to https upload_foos?
If you want all your links to be able to switch between http and https, you have to stop using the _path helper and switch to _url helpers.
After that, using a scope with the protocol parameter forced and protocol constraint makes the urls automatically switch.
routes.rb
scope :protocol => 'https://', :constraints => { :protocol => 'https://' } do
resources :sessions
end
resources :gizmos
And now in your views:
<%= sessions_url # => https://..../sessions %>
<%= gizmos_url # => http://..../gizmos %>
Edit
This doesn't fix urls that go back to http when you are in https. To fix that you need to override url_for.
In any helper
module ApplicationHelper
def url_for(options = nil)
if Hash === options
options[:protocol] ||= 'http'
end
super(options)
end
end
This will set the protocol to 'http' unless it was explicitly set (in routes or when calling the helper).
This was a long time ago and I'm sure it can be improved, but back on some old version of rails I had this code in application controller. Not sure this is still valid for Rails 3, but it may be of some help:
private
SECURE_ACTIONS = {
:login => ["login", "login_customer", "remind_password", "add_customer", "add_or_login_customer"],
:store => ["checkout", "save_order"],
:order => ["show"] }
# Called as a before_filter in controllers that have some https:// actions
def require_ssl
unless ENV['RAILS_ENV'] != 'production' or #request.ssl?
redirect_to :protocol => 'https://', :action => action_name
# we don't want to continue with the action, so return false from the filter
return false
end
end
def default_url_options(options)
defaults = {}
if USE_EXPLICIT_HOST_IN_ALL_LINKS
# This will OVERRIDE only_path => true, not just set the default.
options[:only_path] = false
# Now set the default protocol appropriately:
if actions = SECURE_ACTIONS[ (options[:controller] || controller_name).to_sym ] and
actions.include? options[:action]
defaults[:protocol] = 'https://'
defaults[:host] = SECURE_SERVER if defined? SECURE_SERVER
else
defaults[:protocol] = 'http://'
defaults[:host] = NON_SECURE_SERVER if defined? NON_SECURE_SERVER
end
end
return defaults
end
The USE_EXPLICIT_HOST_IN_ALL_LINKS was some global configuration option, but you can ignore this.
In each controller that required https, I'd add before_filter :require_ssl and add that controller name and its methods to SECURE_ACTIONS. This probably can be improved by passing the action names to the before filter, or something.
I want to use a Rake task to cache my sitemap so that requests for sitemap.xml won't take forever. Here's what I have so far:
#posts = Post.all
sitemap = render_to_string :template => 'sitemap/sitemap', :locals => {:posts => #posts}, :layout => false
Rails.cache.write('sitemap', sitemap)
But when I try to run this, I get an error:
undefined local variable or method `headers' for #<Object:0x100177298>
How can I render a template to a string from within Rake?
Here's how I did it:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
av.class_eval do
include ApplicationHelper
end
include ActionController::UrlWriter
default_url_options[:host] = 'mysite.com'
posts = Post.all
sitemap = av.render 'sitemap/sitemap', :posts => posts
Rails.cache.write('sitemap', sitemap)
Note that I converted my template to a partial to make this work
There is a post about how to be able to access ActionView::Base methods and context from rake task.
However, this is a monkeypatch. Why not use the rails' cache mechanism to accomplish caching? :)
Later edit:
The render_to_string function is defined in ActionController::Base context.
Below is a solution on how to make it work from rake tasks, taken from omninerd.
# In a rake task:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
Rails.cache.write(
"cache_var",
av.render(
:partial => "view_folder/some_partial",
:locals => {:a_var => #some_var}
)
)
Recently I wanted to take a rake task defined like Horace Loeb mentioned and translate it into a self contained background job, but it didn't easily translate.
Here is my implementation for Rails 2.3.x because the Rails 3 implementation I found wouldn't work.
# Public: Template to render views outside the context of a controller.
#
# Useful for rendering views in rake tasks or background jobs when a
# controller is unavailable.
#
# Examples
#
# template = OfflineTemplate.new(:users)
# template.render("users/index", :layout => false, :locals => { :users => users })
#
# template = OfflineTemplate.new(ProjectsHelper, PermissionsHelper)
# template.render("projects/recent", :projects => recent_projects)
#
class OfflineTemplate
include ActionController::UrlWriter
include ActionController::Helpers::ClassMethods
# Public: Returns the ActionView::Base internal view.
attr_reader :view
# Public: Convenience method to
delegate :render, :to => :view
# Public: Initialize an offline template for the current Rails environment.
#
# helpers - The Rails helpers to include (listed as symbols or modules).
def initialize(*helpers)
helper(helpers + [ApplicationHelper])
#view = ActionView::Base.new(Rails.configuration.view_path, {}, self)
#view.class.send(:include, master_helper_module)
end
private
# Internal: Required to use ActionConroller::Helpers.
#
# Returns a Module to collect helper methods.
def master_helper_module
#master_helper_module ||= Module.new
end
end
This is available as a gist: https://gist.github.com/1386052.
Then you can use the class above to create an OfflineTemplate to render your views in a rake task:
task :recent_projects => :environment do
template = OfflineTemplate.new(ProjectsHelper, PermissionsHelper)
puts template.render("projects/recent", :projects => recent_projects)
end