In Rails 3 how do I sort an array of strings with special characters.
I have:
[Água, Electricidade, Telefone, Internet, Televisão, Gás, Renda]
However when i invoke sort over the array Água gets sent to the end of the array.
here's my approach:
class String
def to_canonical
self.gsub(/[áàâãä]/,'a').gsub(/[ÁÀÂÃÄ]/,'A')
end
end
['Água', 'Electricidade', 'Telefone', 'Internet', 'Televisão', 'Gás', 'Renda'].sort {|x,y| x.to_canonical <=> y.to_canonical}
this proves to be usefull for other regexp aswell, the to_canonical method can be implemented they way that best suits you, in this example just covered those 2 regexp.
hope this alternative helps.
:)
The approach I used when I ran into the same issue (depends on iconv gem):
require 'iconv'
def sort_alphabetical(words)
# caching and api-wrapper
transliterations = {}
transliterate = lambda do |w|
transliterations[w] ||= Iconv.iconv('ascii//ignore//translit', 'utf-8', w).to_s
end
words.sort do |w1,w2|
transliterate.call(w1) <=> transliterate.call(w2)
end
end
sorted = sort_alphabetical(...)
An alternative would be to use the sort_alphabetical gem.
Related
I know it's not safe to use interpolated strings when calling .where.
e.g. this:
Client.where("orders_count = #{params[:orders]}")
should be rewritten as:
Client.where("orders_count = ?", params[:orders])
Is it safe to use interpolated strings when calling .order? If not, how should the following be rewritten?
Client.order("#{some_value_1}, #{some_value_2}")
Yes, ActiveRecord's “order” method is vulnerable to SQL injection.
No, it is not safe to use interpolated strings when calling .order.
The above answers to my question have been confirmed by Aaron Patterson, who pointed me to http://rails-sqli.org/#order . From that page:
Taking advantage of SQL injection in ORDER BY clauses is tricky, but a
CASE statement can be used to test other fields, switching the sort
column for true or false. While it can take many queries, an attacker
can determine the value of the field.
Therefore it's important to manually check anything going to order is safe; perhaps by using methods similar to #dmcnally's suggestions.
Thanks all.
Short answer is you need to sanitize your inputs.
If the strings you are planning to interpolate come from an untrusted source (e.g. web browser) then you need to first map them to trusted values. You could do this via a hash:
# Mappings from known values to SQL
order_mappings = {
'first_name_asc' => 'first_name ASC',
'first_name_desc' => 'first_name DESC',
'last_name_asc' => 'last_name ASC',
'last_name_desc' => 'last_name DESC',
}
# Ordering options passed in as an array from some source:
order_options = ['last_name_asc', 'first_name_asc']
# Map them to the correct SQL:
order = order_options.map{|o| order_mappings[o] }.compact.join(', ')
Client.order(order)
#Mike explanation is correct. #dmcnally workaround would work. I'm following in a slightly different path mentioned in [Railscast][1] http://railscasts.com/episodes/228-sortable-table-columns
In a nutshell, if you can construct a private method in the controller, in order to sanitize the user input:
Order by name of one your table columns:
private
def sort_column
Client.column_names.include?(params[:sort]) ? params[:sort] : "first_name"
end
Order by other criteria, then use the whitelist construct such as below:
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
end
And your controller method should then look like this:
Client.all.order(sort_column + " " + sort_direction)
Just anther way to Rome. Hope this help.
Let's try this!
# app/models/concern/ext_active_record.rb
module ExtActiveRecord
extend ActiveSupport::Concern
included do
scope :sortable, -> (params) do
return unless params[:sort_by] && params[:sort_dir]
reorder("#{params[:sort_by]}" => "#{params[:sort_dir]}")
end
end
end
# app/models/user.rb
class User < ActiveRecord::Base
include ExtActiveRecord
# ....
end
# app/controllers/user_controller.rb
class UserController < ApplicationController
def index
#users = User.sortable(params).page(params[:page]).per(params[:per])
end
end
Client.order("#{some_value_1}, #{some_value_2}")
should be written as
order = sanitize_sql_array(['%s, %s', some_value_1, some_value_2])
Client.order(order)
I have a query that goes something like this (in song.rb):
def self.new_songs
Song.where(id: Song.grouped_order_published).select_important_stuff
end
Later on in my app, it is then passed the limit and offset, lets say in the controller:
#songs = Song.new_songs.limit(10).offset(10)
The way my app is structured, I'd like to keep this method of setting things, but unfortunately it is really slow as it is limiting the outer query rather than the subquery.
Is there a way I can expose the subquery such that it receives the limit and offset rather than the outer query?
Edit: I should add I am using postgres 9.2.
Edit 2: The reason why I want to do it in this fashion is I am doing pagination and I need to get the "count" of the total number of rows. So I do something like this:
#songs = Song.new_songs
...
#pages = #songs.count / 10
...
render #songs.limit(params[:page]).offset(0)
If I were to change it somehow, I'd have to redo this entirely (which is in a ton of places). By not limiting it until it's actually called, I can do the count in between and then get just the page at the end. I guess I'm looking more for advice on how this can be done with the inner query, without becoming horribly slow as the database grows.
I could not try the solution and I am not a ruby expert either, but as far as I understand the problem you would need an object that passes all method-calls but limit and offset onto the full query and store the limited sub_query in the meantime.
It could probably look like this:
class LimitedSubquery < Object
# sub_query has to be stored so we can limit/offset it
def initialize(sub_query)
#sub_query = sub_query
end
# Make sure everybody knows we can be used like a query
def self.respond_to?(symbol, include_private=false)
super || full_query.respond_to?(symbol, include_private)
end
# Missing methods are probably meant to be called on the whole query
def self.method_missing(method_sym, *arguments, &block)
if full_query.respond_to?(method_sym)
full_query.send(method_sym, *arguments, &block)
else
super
end
end
# Generate the query for execution
def self.full_query
Song.where(id: #sub_query).select_important_stuff
end
# Apply limit to sub_query
def self.limit(*number)
LimitedSubquery.new(#sub_query.limit(*number))
end
# Apply offset to sub_query
def self.offset(*number)
LimitedSubquery.new(#sub_query.offset(*number))
end
end
And than call it like
def new_songs
LimitedSubquery.new(Song.grouped_order_published)
end
Please edit me if I got something wrong!
Regards
TC
You should consider using the will_paginate gem. This keeps you away form the hazzle to calculate all this by hand ;-)
I have a rails 3 helper function. This function simply takes an array of ojects and return images based on the url in each object. For some reason, I cannot seem to print the images...I just get back the array.
for example:
def my_helper(items)
items.each do |item|
image_tag(item)
end
end
this returns the array. I've tried assigning to a variable and outputting, but no luck. I've seen where people say just use item.join('<br/>') but I didn't get that to work.
Help appreicated
This should work:
def my_helper(items)
items.map do |item|
image_tag(item)
end.join('<br/>')
end
The each method returns the original list it iterates over (in your example, items). To get what you want, you can use map:
def my_helper(items)
items.map do |item|
image_tag(item)
end
end
I'm trying to create a 'search box' that matches users by name.
The difficulty is that a user has both a firstname and a surname. Each of those can have spaces in them (eg "Jon / Bon Jovi", or "Neil Patrick / Harris"), and I'm wondering about the most efficient way to ensure the search is carried out on a concatenation of both the firstname and surname fields.
The list of users is quite large, so performance is a concern. I could just throw a "fullname" def in the user model, but I suspect this isn't the wisest move performance wise. My knowledge of multi-column rails indexes is weak, but I suspect there's a way of doing it via an index with a " " in it?
Just to clarify, I don't need fuzzy matching - exact match only is fine...I just need it to be run on a concatenation of two fields.
Cheers...
You could create a new field in your database called full_name with a regular index, then use a callback to populate this whenever the record is saved/updated:
before_save :populate_full_name
protected
def populate_full_name
self.full_name = "#{first_name} #{last_name}"
end
If you can modify the database, you can and should use the solution provided by gjb.
Here is the solution that does not require you to alter the database. Simply gather all the possible first-name/last-name pairs you can get from the search box. Some code:
# this method returns an array of first/last name pairs given the string
# it returns nil when the string does not look like a proper name
# (i.e. "Foo Bar" or "Foo Bar Baz", but not "Foo" or "Foo "
def name_pairs(string)
return nil unless string =~ /^\w+(\s+\w+)+$/
words = string.split(/\s+/) # split on spaces
result = []
# in the line below: note that there is ... and .. in the ranges
1.upto(words.size-1) {|n| result << [words[0...n], words[n..-1]]}
result.collect {|f| f.collect {|nm| nm.join(" ")}}
end
This method gives you an array of two-element arrays, which you can use to create an or query. Here is how the method looks:
#> name_pairs("Jon Bon Jovi")
=> [["Jon", "Bon Jovi"], ["Jon", "Bon Jovi"]]
#> name_pairs("John Bongiovi")
=> [["John", "Bongiovi"]]
#> name_pairs("jonbonjovi")
=> nil
Of course, this method is not perfect (it does not capitalise the names, but you can do it after splitting) and is probably not optimal in terms of speed, but it works. You can also reopen String and add the method there, so you can go with "Jon Bon Jovi".name_pairs.
I'm using RoR and I want to do concurrency safe update queries. For example when I have
var user = User.find(user_id)
user.visits += 1
I get the following SQL code:
SELECT * FROM Users WHERE ID=1 -- find user's visits (1)
UPDATE Users SET Visits=2 WHERE ID=1 -- 1+1=2
But if there are several queries taking place at the same time, there will be locking problems.
According to RoR API I can use :lock => true attribute, but that's not what I want.
I found an awesome function update_counters:
User.update_counters(my_user.id, :visits => 1)
This gives the following SQL code
UPDATE Users SET Visits=Visits+1 WHERE ID=#something
Works great!
Now my question is how can I override the += function to do the update_counters thing instead?
Because
user.visits += 1 # better
User.update_counters(my_user.id, :visits => 1) # than this
UPDATE
I just created the following function
class ActiveRecord::Base
def inc(column, value)
User.update_counters(self.id, column => value)
end
end
Are there any other better ideas?
Don't know about better ideas, but I would define that method to the User class, not the ActiveRecord. And maybe increment_counter (that uses update_counters) would make it a bit more readable?
def inc(column, value)
self.increment_counter(column, value)
end
Haven't tested that and not saying this is definitely better idea, but that's probably how I'd do it.
Update:
And AFAIK you can't override the "+=", because "a += b" just a shortcut for "a = a + b" and you probably don't want to override "=" to use the update_counters :)