Is there a more performant way of creating this array? - ruby-on-rails

for one of my views I want to include a search field with jqueries typeahead function.
The array should contain all the attribute values of a client.
The array for the query is generated the following way:
#clients = []
Client.each do |client|
#clients << client.attributes.values.join(' ')
end
Is this approach performant enough for a dataset of about 3000 entries?
Or is there a better and faster solution?
Thanks in advance.
Cheers,
Patrick
Update
One user mentioned to implement it like this:
#clients = Client.map do |client|
client.attributes.values.join(' ')
end
This is another way to do it. But a benchmark reveals that this is no improvement in performance.
This leaves me with the question: Maybe there is a more performant way, but speaking about a maxium of 3000 records - does it really matter?

You could use .map:
#clients = Client.map do |client|
client.attributes.values.join(' ')
end

Even if ActiveRecord models would implement the map method (which they don't i believe), the two solutions suggested by the OP and #xdazz are time- and memory-complexity-wise equivalent. This can be observed with this simple benchmark:
require 'fruity'
# Dummy client class
class Client < Struct.new(:first_name, :last_name, :position, :company)
class << self
include Enumerable
def each(&block)
5000.times do
yield Client.new('Firstname', 'Lastname', 'CEO', 'Company Inc.')
end
end
end
alias_method :attributes, :to_h
end
compare do
schnika do
clients = []
Client.each do |client|
clients << client.attributes.values.join(' ')
end
nil
end
xdazz do
clients = Client.map do |client|
client.attributes.values.join(' ')
end
nil
end
end
Which will output
schnika is similar to xdazz
Also, when you look at the implementation of map (synonymous to collect), it becomes clear that really nothing else happens than in the OP's method:
static VALUE
rb_ary_collect(VALUE ary)
{
long i;
VALUE collect;
RETURN_ENUMERATOR(ary, 0, 0);
collect = rb_ary_new2(RARRAY_LEN(ary));
for (i = 0; i < RARRAY_LEN(ary); i++) {
rb_ary_push(collect, rb_yield(RARRAY_PTR(ary)[i]));
}
return collect;
}
This translates to:
class Array
def collect
collect = []
self.each do |el|
collect << yield(el)
end
collect
end
end

You probably don't need to retrieve all the attributes (for example 'updated_at'), so the following may be faster:
#clients = Client.select([:name, :email, :id]).map do |client|
client.attributes.values.join(' ')
end
Added the id in case you need to link to the client.

Related

RSpec testing model method

I have this method in my models/images.rb model. I am starting with testing and having a hard time coming up with tests for it. Would appreciate your help.
def self.tags
t = "db/data.csv"
#arr = []
csvdata = CSV.read(t)
csvdata.shift
csvdata.each do |row|
row.each_with_index do |l, i|
unless l.nil?
#arr << l
end
end
end
#arr
end
First off a word of advice - CSV is probably the worst imaginable data format and is best avoided unless absolutely unavoidable - like if the client insists that manipulating data in MS Excel is a good idea (it is not).
If you have to use CSV don't use a method name like .tags which can confused for a regular ActiveRecord relation.
Testing methods that read from the file system can be quite difficult.
To start with you might want to alter the signature of the method so that you can pass a file path.
def self.tags(file = "db/data.csv")
# ...
end
That way you can pass a fixture file so that you can test it deterministically.
RSpec.describe Image do
describe "tags" do
let(:file) { Rails.root.join('spec', 'support', 'fixtures', 'tags.csv') }
it 'returns an array' do
expect(Image.tags(file)).to eq [ { foo: 'bar' }, { foo: 'baz' } ]
end
end
end
However your method is very ideosyncratic -
def self.tags
t = "db/data.csv"
#arr = []
self.tags makes it a class method yet you are declaring #arr as an instance variable.
Additionally Ruby's enumerable module provides so many methods for manipulating arrays that using an outer variable in a loop is not needed.
def self.tags(file = "db/data.csv")
csv_data = CSV.read(file)
csv_data.shift
csv_data.compact # removes nil elements
end

How to create an array of methods in rails

def id_attachment_require_upload?
!object.id_attachment?
end
...
def work_attachment_require_upload?
!object.work_attachment?
end
I want to make it like below.
array = %w(id address work)
array.each do |a|
def #{a}_attachment_require_upload?
!object.#{a}_attachment?
end
end
Is there any way for me to create a array of methods automatically in rails to save me from the redundant work.
array = %w(id address work)
array.each do |a|
define_method "#{a}_attachment_require_upload?" do
!object.public_send("#{a}_attachment?")
end
end
Arup's answer looks like it's the way to go but I'm not sure if object.#{a}_attachment? will work. If it does, then I learned something new today. You can also use public_send.
array = %w[id address work]
array.each do |a|
define_method "#{a}_attachment_require_upload?" do
!object.public_send("#{a}_attachment?")
end
end

Rails: How to initialize an object with the attributes in strings?

Probably been working on this too long, sloppy design, or both. My issue is I have a model I wish to initialize. The object has like 52 attributes, but I'm only setting a certain ~25 depending on which object I've just scanned. When I scan an object I get the columns and match them up with a hash_map I've created.
Example Hash Map
This just matches the scanned text to their respective attribute name.
hash_map = {"Pizza."=>"pizza_pie","PastaBowl"=>"pasta_bowl","tacos"=>"hard_shell_taco","IceCream"=>"ice_cream","PopTarts"=>"pop_tart"}
What I want to do
menu = RestaurantMenu.new(pizza_pie => var1, pasta_bowl => var2, ...)
My only problem is in my code at the moment I have this...
t.rows.each do |r|
for i in 0..r.length-1
#hash_map[t.combined_columns[i]] => r.[i]
puts "#{hash_map["#{t.combined_columns[i]}"]} => #{r[i]}"
end
end
the puts line displays what I want, but unsure how to get that in my app properly.
Here is several ways to fix this:
hash_map = {"Pizza."=>"pizza_pie","PastaBowl"=>"pasta_bowl","tacos"=>"hard_shell_taco","IceCream"=>"ice_cream","PopTarts"=>"pop_tart"}
attributes.each do |attribute, element|
message.send((attribute + '=').to_sym, hash_map[element])
end
or like this:
class Example
attr_reader :Pizza, :PastaBowl #...
def initialize args
args.each do |k, v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
end
for more details click here
I ended up doing the following method:
attributes = Hash[]
attributes["restaurant"] = tmp_basic_info.name
attributes["menu_item"] = tmp_basic_info.item_name
t.rows.each do |r|
for i in 0..r.length-1
attributes["other"] = t.other_information
attributes[hash_map[t.combined_columns[i]] = r[i]
end
row = ImportMenuItem.new(attributes)
row.save
end

How to combine procs to filter a list in ruby?

I am trying to implement a filter-by-criteria approach in a little project of mine. I have a Filter class, in which i have filters for name, year, genre. For example:
Filter.new.name("The Shawshank Redemption")
Filter.new.year("1994")
Filter.new.genre("drama")
I also have a list of movies, each being an object that has methods name, year and genre. I want to be able to do the following (MoviesContainer is just a class that has a #movies list):
MoviesContainer.filter Filter.new.name("The Godfather") & Filter.new.year("1972") | Filter.year("1974")
I can easily overload the |, & and ! operators in my Filter class, but I don't know how to combine the filter object so that they become just one object which I can pass to filter.
I'm gladly accepting any ideas. :)
My best idea so far is to create a proc for every Filter.new and then combine them in the &,| and ! methods, but I don't know how. I'm thinking something like this could work, however, it doesn't :D
proc { |movie| proc { |movie| movie.name == "The Godfather" } && proc { |movie| movie.year== "1972" }
and then call this with every of the #movies items.
Can you please help me with the combining procs thing, or perhaps propose a better solution. Thank you.
Maybe something like this?
class Filter
attr_accessor :proc
class <<self; alias :old_new :new end
def self.new attribute, value
old_new.tap{|f| f.proc = ->x{x.send(attribute) == value}}
end
def & other
self.class.old_new.tap{|f| f.proc = ->x{proc.call(x) && other.proc.call(x)}}
end
def | other
self.class.old_new.tap{|f| f.proc = ->x{proc.call(x) || other.proc.call(x)}}
end
def !
self.class.old_new.tap{|f| f.proc = ->x{!proc.call(x)}}
end
end
class Movie
attr_accessor :name, :year, :genre
end
MoviesContainer = [
Movie.new.tap{|m| m.name = "The Godfather"; m.year = "1972"},
Movie.new
]
module Enumerable
def filter f
select(&f.proc)
end
end
filter1 = Filter.new(:name, "The Godfather")
filter2 = Filter.new(:year, "1972")
filter3 = Filter.new(:year, "1974")
MoviesContainer.filter(filter1 & filter2 | filter3)
# => [#<Movie:0x000000017dba08 #name="The Godfather", #year="1972">]

How to change string to a method or a field?

I would like to make a string to become an attribute:
Model:
Author
attr_accessible :book1, book2, book3 etc...
I would like to retrieve 20 books in a lesser command
def do_something
self.book1
self.book2
self.book3
....
end
This is a solution I came up with but how can I make the string become an attribute so I could retrieve the data.
def do_something
count = 0
10.times do
self."book#{count += 1}"
end
end
This will work:
def do_something
count = 0
10.times do
self.send("book#{count += 1}")
end
end
Also try this, it's simpler and it should work as well: self[:book1] or self['book1']
BTW this is weird design. Consider using array of books instead:
attr_accessible :books
...
self.books.each { |book| puts book }
self.books[0] # etc.
You can use eval.
10.times do
eval("self.book#{count} = count}")
end

Resources