Colons as divider with Rails Route? - ruby-on-rails

I would like to use colons as divider in my rails route url instead of forward slashes. Is it possible to do the this?
I'm after something like the following
match '*page_path/:title ":" *section_path ":" :section_title' => 'pages#show'
so for the url food/fruit/apples:cooking:pie:apple_pie would return the parameters:
:page_path = "food/fruit"
:title = "apples"
:section_path = "cooking:pie"
:section_title = "apple_pie"
Is this possible in rails?

Here's an approach :
match 'food/fruit/:id' => 'pages#show' # add constraints on id if you need
class Recipe < ActiveRecord::Base
def self.from_param( param )
title, section_path, section_title = extract_from_param( param )
# your find logic here, ex: find_by_title_and_section_path_and_section_title...
end
def to_param
# build your param string here,
# ex: "#{title}:#{section_path}:#{section_title}"
# Beware ! now all your urls relative to this resource
# will use this method instead of #id.
# The generated param should be unique, of course.
end
private
def self.extract_from_param( param )
# extract tokens from params here
end
end
then in your controller:
#recipe = Recipe.from_param( params[:id] )
note that the use of use the built-in to_param method is optionnal.

Related

Rails gem for using routes inside of the article body

is there any gem allowing to replace string in particular format for links to some controller with parameters?
Example: In the content of article /articles/2/second-article I want to link to a previous article /articles/1/first-article.
Thanks
There is not a "gem" but you can do this easily in your model.
class Article
def next
self.class.where("id > ?", id).first
end
def previous
self.class.where("id < ?", id).last
end
end
Then you can use these in your controller:
#article = Article.find(params[:id])
#next_article = #article.next
#previous_article = #article.previous
Ok,
I have written a helper for that:
module ApplicationHelper
def raw_with_links(text)
text.gsub! /{'(.+?)', (.+?)(,(.+?))*}/ do |full_match|
# The expression returned from this block will be used as the replacement string
# $1 will be the matched content between the ' and ' quotes.
# $2 will be the mathed content in the second group with the route name
#4 will be the matched content in the last group with route parameters, if any
url = Rails.application.routes.url_helpers.send $2, $4
link = "" + $1 + ""
end
raw(text)
end
end
then you can call it in a view like this:
<%= raw_with_links #article.content %>
and here is how to create a link in the text (the route parameter (value 7 in the example bellow) is not mandatory):
class HomeController < ApplicationController
def index
#article.content = "<p>This is the article content with the {'link', other_route_name_path, 7} to the other route.</p>"
end
end
and the result is (considering the route other_route_name is other_route/:id:
<p>This is the article content with the link to the other route.</p>

Rails 4 routing parameter that looks like a controller

I have a Entity model with an entity_type attribute that can be Hospital or Clinic.
I would like to be able to refer to things in the abstract:
/entities
/entities/us
/entities/us/mn+md
And the specific:
/hospitals
/hospitals/us
/hospitals/us/mn+md
I'm having difficulty with the routing. I can't seem to get the entity_type parameter to work.
routes.rb:
get "/:entity_type/(:country_code/(:region_code))" => "entities#index", :constraints => {
:entity_type=>["entities","hospitals","clinics"],
:country_code=>/[a-zA-Z]{2}([\+\,][a-zA-Z]{2})*/,
:region_code=>/[a-zA-Z]{2}([\+\,][a-zA-Z]{2})*/
}
...
# remaining RESTful routes
resources :entities
resources :apps
The entities_controller#index method:
def index
#entities = Entity.all
# probably a better way to do this
#entities = #entities.by_type(params[:entity_type]) if ( params[:entity_type].present? && params[:entity_type]!='entities')
# location-specific; works as expected
#entities = #entities.for_country(params[:country_code]) if params[:country_code].present?
#entities = #entities.for_region(params[:region_code]) if params[:region_code].present?
end
Corresponding entity.rb method:
# probably a better way to do this
def self.by_type(entity_type)
return where("entity_type='#{entity_type.singularize.titleize}'") if entity_type != 'entities'
end
Changed:
:entity_type=>["entities","hospitals","clinics"]
to:
:entity_type=>/(entities|hospitals|clinics)/

Can I make a catch-all redirect rule in my routes?

Is it possible to make a "catch all" redirect rule in my routes, for example something like this:
get "/foo*", to: redirect("/bar$1")
Which will result in these 301s:
/foo -> /bar
/foo/baz -> /bar/baz
/foo/?a=b -> /bar/?a=b
Yes, you can. Here's how I've done this before:
# config/routes.rb
def get_params_blob_if_present(params)
"/#{params[:a]}" if params[:a].present?
end
def get_query_string_params_if_present(params, options = {})
params = params.except(:a) # Don't include blob params
"?#{params.to_query}" if params.any?
end
MyApp::Application.routes.draw do
# ...
get '/foo(/*a)', to: redirect { |params, request| "/foo#{get_params_blob_if_present(params)}#{get_query_string_params_if_present(request.params)}" }
# ...
end
Basically, (/*a) sets up a blob named a. You can then conditionally include the params from this blob in the redirect URL if any are present. Likewise, you can conditionally include query string params in the redirect URL if any are present.

Decimals and commas when entering a number into a Ruby on Rails form

What's the best Ruby/Rails way to allow users to use decimals or commas when entering a number into a form? In other words, I would like the user be able to enter 2,000.99 and not get 2.00 in my database.
Is there a best practice for this?
Does gsub work with floats or bigintegers? Or does rails automatically cut the number off at the , when entering floats or ints into a form? I tried using self.price.gsub(",", "") but get "undefined method `gsub' for 8:Fixnum" where 8 is whatever number I entered in the form.
I had a similar problem trying to use localized content inside forms. Localizing output is relatively simple using ActionView::Helpers::NumberHelper built-in methods, but parsing localized input it is not supported by ActiveRecord.
This is my solution, please, tell me if I'm doing anything wrong. It seems to me too simple to be the right solution. Thanks! :)
First of all, let's add a method to String.
class String
def to_delocalized_decimal
delimiter = I18n::t('number.format.delimiter')
separator = I18n::t('number.format.separator')
self.gsub(/[#{delimiter}#{separator}]/, delimiter => '', separator => '.')
end
end
Then let's add a class method to ActiveRecord::Base
class ActiveRecord::Base
def self.attr_localized(*fields)
fields.each do |field|
define_method("#{field}=") do |value|
self[field] = value.is_a?(String) ? value.to_delocalized_decimal : value
end
end
end
end
Finally, let's declare what fields should have an input localized.
class Article < ActiveRecord::Base
attr_localized :price
end
Now, in your form you can enter "1.936,27" and ActiveRecord will not raise errors on invalid number, because it becomes 1936.27.
Here's some code I copied from Greg Brown (author of Ruby Best Practices) a few years back. In your model, you identify which items are "humanized".
class LineItem < ActiveRecord::Base
humanized_integer_accessor :quantity
humanized_money_accessor :price
end
In your view templates, you need to reference the humanized fields:
= form_for #line_item do |f|
Price:
= f.text_field :price_humanized
This is driven by the following:
class ActiveRecord::Base
def self.humanized_integer_accessor(*fields)
fields.each do |f|
define_method("#{f}_humanized") do
val = read_attribute(f)
val ? val.to_i.with_commas : nil
end
define_method("#{f}_humanized=") do |e|
write_attribute(f,e.to_s.delete(","))
end
end
end
def self.humanized_float_accessor(*fields)
fields.each do |f|
define_method("#{f}_humanized") do
val = read_attribute(f)
val ? val.to_f.with_commas : nil
end
define_method("#{f}_humanized=") do |e|
write_attribute(f,e.to_s.delete(","))
end
end
end
def self.humanized_money_accessor(*fields)
fields.each do |f|
define_method("#{f}_humanized") do
val = read_attribute(f)
val ? ("$" + val.to_f.with_commas) : nil
end
define_method("#{f}_humanized=") do |e|
write_attribute(f,e.to_s.delete(",$"))
end
end
end
end
You can try stripping out the commas before_validation or before_save
Oops, you want to do that on the text field before it gets converted. You can use a virtual attribute:
def price=(price)
price = price.gsub(",", "")
self[:price] = price # or perhaps price.to_f
end
Take a look at the i18n_alchemy gem for date & number parsing and localization.
I18nAlchemy aims to handle date, time and number parsing, based on current I18n locale format. The main idea is to have ORMs, such as ActiveRecord for now, to automatically accept dates/numbers given in the current locale format, and return these values localized as well.
I have written following code in my project. This solved all of my problems.
config/initializers/decimal_with_comma.rb
# frozen_string_literal: true
module ActiveRecord
module Type
class Decimal
private
alias_method :cast_value_without_comma_separator, :cast_value
def cast_value(value)
value = value.gsub(',', '') if value.is_a?(::String)
cast_value_without_comma_separator(value)
end
end
class Float
private
alias_method :cast_value_without_comma_separator, :cast_value
def cast_value(value)
value = value.gsub(',', '') if value.is_a?(::String)
cast_value_without_comma_separator(value)
end
end
class Integer
private
alias_method :cast_value_without_comma_separator, :cast_value
def cast_value(value)
value = value.gsub(',', '') if value.is_a?(::String)
cast_value_without_comma_separator(value)
end
end
end
end
module ActiveModel
module Validations
class NumericalityValidator
protected
def parse_raw_value_as_a_number(raw_value)
raw_value = raw_value.gsub(',', '') if raw_value.is_a?(::String)
Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
end
end
end
end
I was unable to implement the earlier def price=(price) virtual attribute suggestion because the method seems to call itself recursively.
I ended up removing the comma from the attributes hash, since as you suspect ActiveRecord seems to truncate input with commas that gets slotted into DECIMAL fields.
In my model:
before_validation :remove_comma
def remove_comma
#attributes["current_balance"].gsub!(',', '') # current_balance here corresponds to the text field input in the form view
logger.debug "WAS COMMA REMOVED? ==> #{self.current_balance}"
end
Here's something simple that makes sure that number input is read correctly. The output will still be with a point instead of a comma. That's not beautiful, but at least not critical in some cases.
It requires one method call in the controller where you want to enable the comma delimiter. Maybe not perfect in terms of MVC but pretty simple, e.g.:
class ProductsController < ApplicationController
def create
# correct the comma separation:
allow_comma(params[:product][:gross_price])
#product = Product.new(params[:product])
if #product.save
redirect_to #product, :notice => 'Product was successfully created.'
else
render :action => "new"
end
end
end
The idea is to modify the parameter string, e.g.:
class ApplicationController < ActionController::Base
def allow_comma(number_string)
number_string.sub!(".", "").sub!(",", ".")
end
end
You can try this:
def price=(val)
val = val.gsub(',', '')
super
end

URL encoded route to Rails controller

I have a URL encoded resource such as:
http://myurl/users/Joe%20Bloggs/index.xml
This is for a RESTful webservice which uses user logins in the path. The problem is that the controller in rails doesn't seem to decode the %20. I get the following error:
ActionController::RoutingError (No route matches "/Joe%20Bloggs/index.xml" with {:method=>:post}):
What I'm actually trying to do is achieve one of 2 options (using authlogic as my registrations handler):
Either (preferably) allow users to register user names with spaces in them, and have these get routed correctly to my controller. Authlogic by default allows spaces & #/. characters - which is just fine with me if I can make it work...
Or I can restrict authlogic to dissallow the spaces. I know I can do this with:
.merge_validates_format_of_login_field_options...
but I'm not entirely sure of the correct syntax to provide the new regex and return message on failure...
Any suggestions greatly appreciated!
Generally it's a better idea to have a URL-safe "slug" field in your models for situations like this. For example:
class User < ActiveRecord::Base
before_validation :assign_slug
def to_param
# Can't use alias_method on methods not already defined,
# ActiveRecord creates accessors after DB is connected.
self.slug
end
def unique_slug?
return false if (self.slug.blank?)
if (new_record?)
return self.class.count(:conditions => [ 'slug=?', self.slug ]) == 0
else
return self.class.count(:conditions => [ 'slug=? AND id!=?', self.slug, self.id ]) == 0
end
end
def assign_slug
return if (slug.present?)
base_slug = self.name.gsub(/\s+/, '-').gsub(/[^\w\-]/, '')
self.slug = base_slug
count = 1
# Hunt for a unique slug starting with slug1 .. slugNNN
while (!unique_slug?)
self.slug = base_slug + count.to_s
count += 1
end
end
end
This may solve the problem of having non-URL-friendly names. Rails is particularly ornery when it comes to having dots in the output of to_param.

Resources