Rails3: Prefix to all url_for URLS - ruby-on-rails-3

I add i18n to my webpage (different content for different languages). My URLs look loke this:
http://host.tld/de/news/15
http://host.tld/en/news/15
...
All my URLs in the application are set by the link_to/url_for method like this
url_for("/news/#{news.id}/#{urlify(news.title)}")
url_for("/news/#{#news.section}")
...
My routing looks like this:
scope "/:language/", :language => /de|en/ do
match "news/:news_id(/:title)" => "news#show_entry", :constraints => { :news_id => /[0-9]+/ }
...
end
I add this to my ApplicationController:
def default_url_options(options={})
{:language => I18n.locale}
end
Now I want to add the language prefix to ALL the URLs without change all the url_for()-calls. Is there a solution (parameter/config-option or something) to add this prefix? It should work with relative paths, too.

If you are looking not to change all the url for calls you could add a method in the application_helper.rb file to override the existing methods to add in the language
def url_for(options={})
if options[:language]
'/options[:language]' + super
else
super
end
end
def link_tolink_to(*args, &block)
options = args[1] || {}
if options[:language]
'/options[:language]' + super
else
super
end
end

Related

Rails 3: As json with include option does not takes into account as_json redefinition for included association

I've got two models.
Class ModelA < ActiveRecord::Base
has_many :model_bs
end
Class ModelB < ActiveRecord::Base
belongs_to :model_a
def as_json(options = {})
{
:whatever => 'hello world'
}
end
end
When I call model_a.as_json(:include => :model_b), I want it to return a json which includes all model_bs, which it does, but employing my as_json redefinition, which it does not as it just uses the default one. Is there any way to use my own method rather than the original one? Thanks
In Rails 3, as_json method invokes serializable_hash to obtain the attributes hash. And they share the same 'options' parameter. In your case, overwritting serializable_hash would give the expected result.
def serializable_hash(options = {})
{:whatever => 'hello world'}
end
But, My suggestion is that instead of overwriting the convention, operate on the result of "super", which is like:
def serializable_hash(options = {})
hash = super
has[:name] = "hello world"
hash
end

Rails current_page? "fails" when method is POST

I have a really simple problem. I have a page of reports and each report has its own tab. I'm using current_page? to determine which tab should be highlighted. When I submit any report, current_page? doesn't seem to work anymore, apparently because the request method is POST.
Is this the intended behavior of current_page? I have a hard time imagining why that would be the case. If it is, how do people normally get around this problem?
Here's an example of a current_page? call:
<li><%= link_to "Client Retention", reports_client_retention_path, :class => current_page?(reports_client_retention_path) ? "current" : "" %></li>
All right, it looks like I figured out the answer to my own question about 5 minutes after putting up a bounty. It looks like current_page? will always return false on POST.
Here's the source code for current_page?:
# File actionpack/lib/action_view/helpers/url_helper.rb, line 588
def current_page?(options)
unless request
raise "You cannot use helpers that need to determine the current " "page unless your view context provides a Request object " "in a #request method"
end
return false unless request.get?
url_string = url_for(options)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
if url_string.index("?")
request_uri = request.fullpath
else
request_uri = request.path
end
if url_string =~ %r^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
else
url_string == request_uri
end
end
I don't really understand why they would have gone out of their way to make current_page? work only for GET requests, but at least now I know that that's the way it is.
You could create a new current_path? method in your ApplicationHelper:
def current_path?(*paths)
return true if paths.include?(request.path)
false
end
Pass in one or more paths and it returns true if any match the user's current path:
current_path?('/user/new')
current_path?(root_path)
current_path?(new_user_path, users_path '/foo/bar')
Or, you can create a new current_request? helper method to check the Rails controller + action:
def current_request?(*requests)
return true if requests.include?({
controller: controller.controller_name,
action: controller.action_name
})
false
end
Pass in one or more controller + action and it returns true if any match the user's current request:
current_request?(controller: 'users', action: 'new')
current_request?({controller: 'users', action: 'new'}, {controller: 'users', action: 'create'})
==UPDATE==
Ok, I decided to make using current_request? a little less verbose by not requiring that you type out the controller when you are trying to match multiple actions:
def current_request?(*requests)
requests.each do |request|
if request[:controller] == controller.controller_name
return true if request[:action].is_a?(Array) && request[:action].include?(controller.action_name)
return true if request[:action] == controller.action_name
end
end
false
end
Now you can do this:
current_request?(controller: 'users', action: ['new', 'create'])
I was having the same problem when using POST. My solution was to do something like this
def menu_item link_text, link_path
link_class = (request.original_url.end_with? link_path) ? 'active' : ''
content_tag :li, link_to(link_text, link_path), class: link_class
end
where link_path is just url_for(action: 'action', controller: 'controller')

Rails 3: Controller params default value

I am using a remote form_for for my show action to retrieve content based on the params passed by this form.
= form_tag modelname_path(#modelname), :id=>"select_content_form", :remote => true, :method => 'get' do
= text_field_tag :content_type, params[:content_type], :id=>"select_content_type"
= submit_tag "submit", :name => nil, :id=>"select_content_submit"
And I alter the content in controller as follows:
# Default params to "type1" for initial load
if params[:content_type]
#content_type = params[:content_type];
else
#content_type = "type1"
end
case #content_type
when "type1"
# get the content
#model_content = ...
when "type1"
# get the content
#model_content = ...
My question is, whether the above approach is the only we can set defaults for params or can we do it in a better manner. This works but I would like to know if this is the right approach.
UPDATE
Based on the suggestion below, I used the following and got an error on defaults.merge line:
defaults = {:content_type=>"type1"}
params = defaults.merge(params)
#content_type = params[:content_type]
A good way of setting default options is to have them in a hash, and merge your incoming options onto it. In the code below, defaults.merge(params) will overwrite any values from the params hash over the default ones.
def controller_method
defaults = {:content=>"Default Content", :content_type=>"type1"}
params = defaults.merge(params)
# now any blank params have default values
#content_type = params[:content_type]
case #content_type
when "type1"
#model_content = "Type One Content"
when "type2"
#etc etc etc
end
end
If there is a static list of types you could make it a dropdown box and just don't include a blank option so that something is always selected. But if you're stuck with a textbox you could clean up the controller action by using a before filter:
class FoosController < ActionController::Base
before_filter :set_content_type, :only => [:foo_action]
def foo_action
...
end
protected
def set_content_type
params[:content_type] ||= "type1"
end
end
I wanted to add to this discussion a working way to set default params:
defaults = { foo: 'a', bar: 'b' }
params.replace(defaults.merge(params))
This avoids assigning a local variable via "params =".

Rails assigning names to variables

I'm building a user ranking system, and am trying to assign user.rank values with a name.
I wanted to define something like this in my User model and then be able to reference it when displaying each user's rank, but this probably isn't the best way:
class User < ActiveRecord::Base
RANK_NAMES = {
'Peasant' => (0..75),
'Craftsman' => (76..250),
'Vassal' => (251..750),
'Noble' => (750..1500),
'Monarch' => (1501..999999)
}
Perhaps it would be better to define a method in a controller or helper like:
if user.rank == 0..75
rank_name = "Peasant"
elsif...
But not sure how to do that. Anyone have any thoughts? I'm not even sure what to call what it is I'm trying to do, thus making it difficult to research on my own.
It could be something even as simple as this, assuming user.rank exists.
class User < ActiveRecord::Base
...
def rank_name
case self.rank
when 0..75
'Peasant'
when 76..250
'Craftsman'
when 251..750
'Vassal'
when 750..1500
'Noble'
when 1501..999999
'Monarch'
end
end
...
end
If rank_name is specific to the User, I'd make it a method of User.
You could try something like below. It might give you some ideas.
class User
RANKS = [
{:name => 'Peasant', :min => 0, :max => 75},
{:name => 'Craftsman', :min => 76, :max => 250}
# ...
]
attr_accessor :rank
def rank_name
# TODO what happens if rank is out of range of all ranks or rank is nil
# or not an integer
User::RANKS[rank_index][:name]
end
private
def rank_index
User::RANKS.index { |r| (r[:min]..r[:max]).include? #rank }
end
end
user = User.new
user.rank = 76
puts user.rank_name # -> Craftsman

override to_xml to limit fields returned

using ruby 1.9.2 and rails 3, i would like to limit the fields returned when a record is accessed as json or xml (the only two formats allowed).
this very useful post introduced me to respond_with and i found somewhere online that a nice way to blanket allow/deny some fields is to override as_json or to_xml for the class and set :only or :except to limit fields.
example:
class Widget < ActiveRecord::Base
def as_json(options={})
super(:except => [:created_at, :updated_at])
end
def to_xml(options={})
super(:except => [:created_at, :updated_at])
end
end
class WidgetsController < ApplicationController
respond_to :json, :xml
def index
respond_with(#widgets = Widgets.all)
end
def show
respond_with(#widget = Widget.find(params[:id]))
end
end
this is exactly what i am looking for and works for json, but for xml "index" (GET /widgets.xml) it responds with an empty Widget array. if i remove the to_xml override i get the expected results. am i doing something wrong, and/or why does the Widgets.to_xml override affect the Array.to_xml result?
i can work around this by using
respond_with(#widgets = Widgets.all, :except => [:created_at, :updated_at])
but do not feel that is a very DRY method.
In your to_xml method, do the following:
def to_xml(options={})
options.merge!(:except => [:created_at, :updated_at])
super(options)
end
That should fix you up.