Ransack gem - Case insensitive sort (Rails 4) - sorting

Using the Ransack gem, I'm trying to sort a simple list on an index page. However, there are both uppercase and lowercase values in the sort field name. To make them sorted useful, the sort should be case insensitive.
This is the model Vendor:
name:string, email:string, address:string, phone:string, fax:string
This is the controller:
class VendorsController < ApplicationController
def index
#search = Vendor.search(params[:q])
#vendors = #search.result
end
And the view:
/index.html.slim
tr
th = sort_link #search, :name, "Name"
th = sort_link #search, :email, "Company Email"
th = sort_link #search, :address, "Address"
Any advice would be greatly appreciated.

What you're looking for is a custom "Ransacker":
class Vendor < ActiveRecord::Base
ransacker :name_case_insensitive, type: :string do
arel_table[:name].lower
end
end
view:
th = sort_link(#q, :name_case_insensitive)

You can solve this for all string columns of a model at once by using this approach:
# lib/ransack_object.rb
module RansackObject
def self.included(base)
base.columns.each do |column|
if column.type == :string
base.ransacker column.name.to_sym, type: :string do
Arel.sql("lower(#{base.table_name}.#{column.name})")
end
end
end
end
end
Then include the ransack object in your model:
class UserWithManyAttributes < ActiveRecord::Base
include RansackObject
end

10 months, maybe too late!!
This solved my problem with posgresql
sort_link #search, 'lowercase(name)', "Name"

Ransack now has a case_insensitive option
you can custom a predicate with case_insensitive: false
Refrences: https://github.com/activerecord-hackery/ransack/wiki/Custom-Predicates

Thanks to hd1 who posted the approach above.
I am posting the actual code here - it's not ideal, since it seems like a wordy workaround.
Still open to cleaner suggestions.
def index
#search = Vendor.search(params[:q])
#vendors = #search.result
if params[:q].present?
params[:q].each do |k, v|
if v == 'name asc'
#vendors = #search.result.sort { |p1, p2| p1.name.downcase <=> p2.name.downcase }
elsif v == 'name desc'
#vendors = #search.result.sort { |p2, p1| p1.name.downcase <=> p2.name.downcase }
end
end
end
end

Related

Why does Ruby Kirbybase doesn't work with if statement?

I recently started using kirbybase in ruby, but I ran into a problem using the if statement with a result set. Here's a semplified code that seems to have this problem:
require 'kirbybase'
db = KirbyBase.new
if db.table_exists?(:database)
db.drop_table(:database)
end
list = db.create_table(:database, :name, :String, :password, :String, :test, :String)
name = 'Test'
password = 'abcde'
list.insert(name, password, nil)
account = list.select { |r| r.name == name}
if account.test.nil?
puts 'right'
else
puts 'wrong'
end
Why does it output "wrong"?
It seems to be strange to answer my own question but I solved the problem: account.test is an array so the correct form of the if statement is:
if account.test[0].nil?
puts 'right'
else
puts 'wrong'
end

How to make more efficient code in model?

Any idea to refactor the code of the method self.import_data ? It's a method which allow the application to save CSV file in database (with some restriction on the user email). It's supposed to run every day at noon so it has to be quick.
Currently its very long to run when I have a big CSV file. I wonder if there a way to make this code more efficient and win some time (or to avoiding the loop or make less request...). I don't really know what makes the process so long actually and how to correct it.
Here is my model :
class Person < ActiveRecord::Base
has_paper_trail
validates :email, uniqueness: true
require 'csv'
def is_former_email?(update_email)
self.versions.each do |version|
next if version.object.nil?
return true if version.object.include?(update_email)
end
end
def self.import_data
filename = File.join Rails.root, '/vendor/people.csv'
CSV.foreach(filename, headers: true, col_sep: ',') do |row|
firstname, lastname, home_phone_number, mobile_phone_number, email, address = row
person = Person.find_or_create_by(firstname: row["firstname"], lastname: row['lastname'], address: row['address'] )
if person.is_former_email?(row['email']) == true
puts "not allowed"
else
person.update_attributes({firstname: row['firstname'], lastname: row['lastname'], home_phone_number: row['home_phone_number'], mobile_phone_number: row['mobile_phone_number'], address: row['address'], email: row['email']})
end
end
end
end
I was a little refactored your code, but for more efficiently I recommend to use gem activerecord-import and optimize versions model for search previous emails.
class Person < ActiveRecord::Base
require 'csv'
FILE_NAME = File.join Rails.root, '/vendor/people.csv'
validates :email, uniqueness: true
has_paper_trail
def self.import_data
people = CSV.new(File.new(FILE_NAME), headers: true, header_converters: :symbol, converters: :all).to_a.map(&:to_hash)
versions_by_item_id = Version.where(item_type: 'Person').select('item_id, object').group_by(&:item_id)
people.each do |person_params|
person = Person.find_or_create_by(person_params.slice(:firstname, :lastname, :address))
if versions_by_item_id[person.id] && versions_by_item_id[person.id].sum { |v| v.object.to_s }.include?(person_params[:email])
puts 'not allowed'
else
person.update_attributes(person_params.slice(:home_phone_number, :mobile_phone_number, :email))
end
end
end
end

How to filter by foreign id and local attribute via belongs_to?

The following models are linked via belongs_to:
require 'mongoid'
class Sensor
include Mongoid::Document
field :sensor_id, type: String
validates_uniqueness_of :sensor_id
end
...
require 'mongoid'
require_relative 'sensor.rb'
class SensorData
include Mongoid::Document
belongs_to :sensor
field :date, type: Date
field :ozonMax1h, type: Float
field :ozonMax8hMittel, type: Float
index({ date: 1, sensor_id: 1 }, { unique: true })
end
Here is a Sinatra app which provides a few API paths based on these models:
require 'sinatra'
require 'csv'
require_relative './models/sensor.rb'
require_relative './models/sensor_data.rb'
configure do
Mongoid.load!('./mongoid.yml')
end
def prepare_for_export(sensor_data)
converted_data = sensor_data.asc(:date).map do |e|
{
sensor_id: e.sensor.nil? ? :null : e.sensor.sensor_id,
date: e.date,
ozonMax1h: e.ozonMax1h,
ozonMax8hMittel: e.ozonMax8hMittel
}
end
converted_data
end
def convert_to_json(sensor_data)
prepare_for_export(sensor_data).to_json
end
def convert_to_csv(sensor_data)
data = prepare_for_export sensor_data
csv_string = CSV.generate do |csv|
csv << data.first.keys
data.each do |hash|
csv << hash.values
end
end
csv_string
end
def get_recent
max_date = SensorData.max(:date)
SensorData.where(date: max_date)
end
def get_for_year(year)
SensorData.where(:date.gte => Date.new(year, 1, 1)).where(:date.lte => Date.new(year, 12, 31))
end
def get_for_sensor(sensor)
foo = SensorData.where(sensor_id: sensor)
puts "hallo"
return foo
end
get '/api/v1/stations' do
content_type :json
Sensor.all.map { |e| {sensor_id: e.sensor_id} }.to_json
end
get '/api/v1/sensordata/:year' do
content_type :json
convert_to_json get_for_year(params[:year].to_i)
end
get '/api/v1/sensordata/:year/csv' do
convert_to_csv get_for_year(params[:year].to_i)
end
get '/api/v1/recent' do
content_type :json
convert_to_json get_recent
end
I would like to output the SensorData for a particular sensor such as here:
/api/v1/stations/:sensor_id/sensordata/:year/csv
I am not sure what you are trying to do or even if you are still looking for an answer but here it goes. Something seems wrong with the models in the example you have here. Sounds like part of what you are doing would work if Sensor knows about sensor_data. So might need to add this to Sensor class:
has_many :sensor_data
Though the singular of data is datum. The class would be expected to be SensorDatum. If you can't change it, you need to tell Mongoid the class_name to expect in the has_many is actuall SensorData.
You CAN specify foreign_key in Mongoid with belongs_to.
You CANNOT filter with the belongs_to like you can with ActiveRecord, but you can use scopes outside of the belongs_to to get the same effect. Exampe:
belongs_to :sensor
scope :for_year, -> (year) { where(:date.gte => Date.new(2015,1,1)).where(:date.lte => Date.new(2015, 12, 31))}
or
belongs_to :sensor
def self.for_year year
where(:date.gte => Date.new(year,1,1)).where(:date.lte => Date.new(year, 12, 31))
end
So your query would become something like this:
sensor = Sensor.find_by(sensor_id: params[:sensor_id])
sensor.sensor_data.for_year(2015)

Rails 3 serialized model field form_for and field_for not generating correct name

I have this model:
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_page_objects
entry_pages.map { |url| EntryPage.new(url) }
end
def entry_page_objects_attributes=(attributes)
# ...
end
....
end
This form to render the model:
.....
%p
%p
= crawler_form.label 'Entry pages'
= crawler_form.text_area :entry_pages_text, size: '80x6'
%ul.entry-pages
= crawler_form.fields_for :entry_page_objects do |entry_page_field|
%li=entry_page_field.text_field :url, size: 80
%a{href: '#', class: 'add-button'} Add Entry Page
The problem I have is that the form renders the entry_page_object input names incorrectly(e.g. company_crawler[entry_page_objects_attributes][0][url] instead of company_crawler[entry_page_objects][0][url]). I am really not sure what to do, I have read the documentation and the example says that just by defining attr_attributes=(attributes) and persisted? I will be able to use fields_for for collections just if they were associations defined with accept_nested_fields.
I have seen different solutions like just giving String 'entry_page_objects[]' to fields_for but I want to be consistent with rails naming convention and I know I can use form_tag instead of form_for but I want to make fields_for work as expected.
Here is some information for all that have not understood properly how nested_attributes works, like me.
What I have reported as issue is actually how it is supposed to work. When we have, let say, this model:
class Foo < ActiveRecord::Base # it has name attribute
has_many :larodis
accepts_nested_attributes_for :larodi
end
class Larodi < ActiveRecord::Base # it has name attribute
belongs_to :foo
end
This definition gives me the possibility to create Foo with many Larodi's just by giving a hash of parameters. For example:
x = Foo.create(name: 'Josh', larodi_attributes: [ {name: 'Wayne'} ]
x.larodis.map(&:name) # ['Wayne']
Now comes the part where #field_for understands if we have nested attribute to work with. We check this by looking for name_attributes= method. If it is defined #fields_for generates form of the type <input ... name=object[name][INDEX][method]>... where index is just an integer.
Keep in mind that when implementing custom name_attibutes(attributes) you must check attributes type - it can be Array like the example, it can be Hash of this type:
{ 1 => { ... } , 2 => { ... } }
Just like a hash representing array, where the key is index and value is the value for this index.
The answear looks like this:
_form.html.haml
....
= crawler_form.fields_for :entry_pages do |entry_page_field|
%li
=entry_page_field.text_field :url, size: 80
...
company_crawler.rb
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_pages_attributes=(attributes)
self.entry_pages = attributes_collection(attributes).map do |attribute|
EntryPage.new(attribute[:url])
end
end
def entry_pages=(entry_pages)
entry_pages = entry_pages.map do |entry_page|
cast_entry_page_to_entry_page_object(entry_page)
end
write_attribute(:entry_pages, entry_pages)
end
...
private
def attributes_collection(attributes)
case attributes
when Array
attributes
when Hash
attributes.values
end
end
def cast_entry_page_to_entry_page_object(entry_page)
case entry_page
when String
EntryPage.new(entry_page)
when EntryPage
entry_page
end
end
end
For clarity I have removed entry_page_objects and use only entry_pages.

ActiveRecord::Base Without Table

This came up a bit ago ( rails model attributes without corresponding column in db ) but it looks like the Rails plugin mentioned is not maintained ( http://agilewebdevelopment.com/plugins/activerecord_base_without_table ). Is there no way to do this with ActiveRecord as is?
If not, is there any way to get ActiveRecord validation rules without using ActiveRecord?
ActiveRecord wants the table to exist, of course.
This is an approach I have used in the past:
In app/models/tableless.rb
class Tableless < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default,
sql_type.to_s, null)
end
# Override the save method to prevent exceptions.
def save(validate = true)
validate ? valid? : true
end
end
In app/models/foo.rb
class Foo < Tableless
column :bar, :string
validates_presence_of :bar
end
In script/console
Loading development environment (Rails 2.2.2)
>> foo = Foo.new
=> #<Foo bar: nil>
>> foo.valid?
=> false
>> foo.errors
=> #<ActiveRecord::Errors:0x235b270 #errors={"bar"=>["can't be blank"]}, #base=#<Foo bar: nil>>
Validations are simply a module within ActiveRecord. Have you tried mixing them into your non-ActiveRecord model?
class MyModel
include ActiveRecord::Validations
# ...
end
I figure the more answers the better since this is one of the first results in google when searching for "rails 3.1 models without tables"
I've implements the same thing without using ActiveRecord::Base while including the ActiveRecord::Validations
The main goal was to get everything working in formtastic, and below I've included a sample payment that will not get saved anywhere but still has the ability to be validated using the validations we all know and love.
class Payment
include ActiveModel::Validations
attr_accessor :cc_number, :payment_type, :exp_mm, :exp_yy, :card_security, :first_name, :last_name, :address_1, :address_2, :city, :state, :zip_code, :home_telephone, :email, :new_record
validates_presence_of :cc_number, :payment_type, :exp_mm, :exp_yy, :card_security, :first_name, :last_name, :address_1, :address_2, :city, :state
def initialize(options = {})
if options.blank?
new_record = true
else
new_record = false
end
options.each do |key, value|
method_object = self.method((key + "=").to_sym)
method_object.call(value)
end
end
def new_record?
return new_record
end
def to_key
end
def persisted?
return false
end
end
I hope this helps someone as I've spent a few hours trying to figure this out today.
UPDATE: For Rails 3 this can be done very easy. In Rails 3+ you can use the new ActiveModel module and its submodules. This should work now:
class Tableless
include ActiveModel::Validations
attr_accessor :name
validates_presence_of :name
end
For more info, you can check out the Railscast (or read about it on AsciiCasts) on the topic, as well as this blog post by Yehuda Katz.
OLD ANSWER FOLLOWS:
You may need to add this to the solution, proposed by John Topley in the previous comment:
class Tableless
class << self
def table_name
self.name.tableize
end
end
end
class Foo < Tableless; end
Foo.table_name # will return "foos"
This provides you with a "fake" table name, if you need one. Without this method, Foo::table_name will evaluate to "tablelesses".
Just an addition to the accepted answer:
Make your subclasses inherit the parent columns with:
class FakeAR < ActiveRecord::Base
def self.inherited(subclass)
subclass.instance_variable_set("#columns", columns)
super
end
def self.columns
#columns ||= []
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
# Overrides save to prevent exceptions.
def save(validate = true)
validate ? valid? : true
end
end
This is a search form that presents an object called criteria that has a nested period object with beginning and end attributes.
The action in the controller is really simple yet it loads values from nested objects on the form and re-renders the same values with error messages if necessary.
Works on Rails 3.1.
The model:
class Criteria < ActiveRecord::Base
class << self
def column_defaults
{}
end
def column_names
[]
end
end # of class methods
attr_reader :period
def initialize values
values ||= {}
#period = Period.new values[:period] || {}
super values
end
def period_attributes
#period
end
def period_attributes= new_values
#period.attributes = new_values
end
end
In the controller:
def search
#criteria = Criteria.new params[:criteria]
end
In the helper:
def criteria_index_path ct, options = {}
url_for :action => :search
end
In the view:
<%= form_for #criteria do |form| %>
<%= form.fields_for :period do |prf| %>
<%= prf.text_field :beginning_as_text %>
<%= prf.text_field :end_as_text %>
<% end %>
<%= form.submit "Search" %>
<% end %>
Produces the HTML:
<form action="/admin/search" id="new_criteria" method="post">
<input id="criteria_period_attributes_beginning_as_text" name="criteria[period_attributes][beginning_as_text]" type="text">
<input id="criteria_period_attributes_end_as_text" name="criteria[period_attributes][end_as_text]" type="text">
Note: The action attribute provided by the helper and the nested attributes naming format that makes it so simple for the controller to load all the values at once
There is the activerecord-tableless gem. It's a gem to create tableless ActiveRecord models, so it has support for validations, associations, types. It supports Active Record 2.3, 3.0, 3.2
The recommended way to do it in Rails 3.x (using ActiveModel) has no support for associations nor types.

Resources