Using a variable column name in a rails helper method where( ) query - ruby-on-rails

I'm working with a User model that includes booleans for 6 days
[sun20, mon21, tue22, wed23, thur24, fri25]
with each user having option to confirm which of the 6 days they are participating in.
I'm trying to define a simple helper method:
def day_confirmed(day)
User.where(day: true).count
end
where I could pass in a day and find out how many users are confirmed for that day, like so:
day_confirmed('mon21')
My problem is that when I use day in the where(), rails assumes that I'm searching for a column named day instead of outputting the value that I'm passing in to my method.
Surely I'm forgetting something, right?

This syntax:
User.where( day: true )
Is equivalent to:
User.where( :day => true )
That's because using : in a hash instead of =>, e.g. { foo: bar }, is the same as using a symbol for the key, e.g. { :foo => bar }. Perhaps this will help:
foo = "I am a key"
hsh = { foo: "bar" }
# => { :foo => "bar" }
hsh.keys
# => [ :foo ]
hsh = { foo => "bar" }
# => { "I am a key" => "bar" }
hsh.keys
# => [ "I am a key" ]
So, if you want to use the value of the variable day rather than the symbol :day as the key, try this instead:
User.where( day => true )

If these are your column names, [sun20, mon21, tue22, wed23, thur24, fri25]
And you are calling day_confirmed('mon21') and trying to find the column 'mon21' where it is true, you can use .to_sym on the date variable
def day_confirmed(day)
User.where(day.to_sym => true).count
end
the .to_sym will get the value of date, and covert it to :mon21

Related

Ruby condition for inserting unique items into an array

I know that if you have an array and reference it as array.uniq it will return without any of the duplicates.
However in this case it is an array of objects (is that proper ruby speak?). I want each call to go into the #calls array unless the call.from is the same as a call_formatted object already present in the array.
How can I conditionally place these objects in the array if no other objects in the array have the same call.from value?
calls_raw.each do |call|
call_formatted = {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
#calls << call_formatted
end
array.uniq { |item| item[:from] }
Use #map to build your array for you and call #uniq on it...
calls_raw.map do |call|
{
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.uniq{|call| call[:from]}
The above approach will first build an array of calls larger than it may ultimately need to be, and the final call to #uniq will make the list unique.
Or, to avoid adding all the duplicates in the array, you could build it with a Hash as such:
calls_raw.each_with_object do |call, h|
h[call.from] ||= {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.values
The Hash approach will use the first occurrence of call.from as it is being set with ||=. To use the last occurrence of call.from then use a straightforward assignment with =.
It's also been suggested to just use a Set instead of an Array.
To take that approach you're going to have to implement #eql? and #hash on the class we're populating the set with.
class CallRaw
attr_accessor :from
def initialize(from)
self.from = from
end
def eql?(o)
# Base equality on 'from'
o.from == self.from
end
def hash
# Use the hash of 'from' for our hash
self.from.hash
end
end
require 'set'
s = Set.new
=> <Set: {}>
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# We expect now, that adding another will not grow our set any larger
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# Great, it's not getting any bigger
s << CallRaw.new("Chewbaca")
s << CallRaw.new("Chewbaca")
=> <Set: {#<CallRaw:0x00000002211888 #from="Chewbaca">}>
Awesome - the Set works!!!
Now, it is interesting to note that having implemented #eql? and #hash, we can now use Array#uniq without having to pass in a block.
a = Array.new
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">, <CallRaw:0x000000021c2bc0 #from="Chewbaca">]
a.uniq
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
Now, I'm just wondering if there is a badge that StackOverflow awards for having too much coffee before setting out to answer a question?
Unless there's some reason it has to be an array, I'd store the data in a Hash, keyed by the from value.
Then it's easy and fast to look up an entry by the from value. You can choose to insert a new value only if there's no value already with the same key, or insert the new value and let it replace the old entry with that key.
Example:
calls = Hash.new
def add(call)
if not calls[call.from]
calls[call.from] = call
end
end

Selecting hash elements by comparing with array

I am looking for the Ruby/Rails way to approach the classic "select items from a set based on matches with another set" task.
Set one is a simple hash, like this:
fruits = {:apples => "red", :oranges => "orange", :mangoes => "yellow", :limes => "green"}
Set two is an array, like this:
breakfast_fruits = [:apples, :oranges]
The desired outcome is a hash containing the fruits that are listed in Breakfast_fruits:
menu = {:apples => "red", :oranges => "orange"}
I've got a basic nested loop going, but am stuck on basic comparison syntax:
menu = {}
breakfast_fruits.each do |brekky|
fruits.each do |fruit|
//if fruit has the same key as brekky put it in menu
end
end
I'd also love to know if there is a better way to do this in Ruby than nested iterators.
You can use Hash#keep_if:
fruits.keep_if { |key| breakfast_fruits.include? key }
# => {:apples=>"red", :oranges=>"orange"}
This will modify fruits itself. If you don't want that, a little modification of your code works:
menu = {}
breakfast_fruits.each do |brekky|
menu[brekky] = fruits[brekky] if breakfast_fruits.include? brekky
end
ActiveSupport (which comes with Rails) adds Hash#slice:
slice(*keys)
Slice a hash to include only the given keys. Returns a hash containing the given keys.
So you can say things like:
h = { :a => 'a', :b => 'b', :c => 'c' }.slice(:a, :c, :d)
# { :a => 'a', :c => 'c' }
In your case, you'd splat the array:
menu = fruits.slice(*breakfast_fruits)

SQL-like syntax for array of objects

I have an array of objects. I need to use an SQL-like condition WHERE field like '%value%' for some object fields in this array.
How to do it?
Edited:
For example I have array with Users and I need to find all users with first_name like ike and email like 123.
Edited2:
I need method to get Users with first_name like smth and email like smth from ARRAY of my Users. Users have first_name and email.
Edited3:
All my users are in database. But I have some business logic, at the end of this logic I have array with Users. Next I need to filter this array with some text: ike for first_name and 123 for email. How to do it?
arr = %w[hello quick bool boo foo]
arr.select { |x| x.include?("foo") }
=> ["bool", "boo", "foo"]
or in your case, if you have an array of objects, you can do:
x.first_name.include?("foo") && x.email.include?("123")
For more customization, you can use Array#select with Regexeps
If you can just use ruby methods for this that do something like this:
User = Struct.new(:email, :first_name) # Just creating a cheap User class here
users = [
User.new('1#a.com' , 'ike'),
User.new('123#a.com', 'bob'),
User.new('123#a.com', 'ike'),
]
# results will be an array holding only the last element in users
results = users.find_all do |user|
user.email =~ /123/ and
user.first_name =~ /ike/
end
Writing your own sql parser seems like a pretty bad idea, but if you really need to parse simple SQL where clauses you could do something like this:
User = Struct.new(:email, :first_name) # Just creating a cheap User class here
users = [
User.new('1#a.com' , 'ike'),
User.new('123#a.com', 'bob'),
User.new('123#a.com', 'ike'),
]
def where(array, sql)
sql = sql.gsub(/\s+AND\s+/, ' ') # remove AND's
terms = Hash[ *sql.split(/\s+LIKE\s+| /) ] # turn "a LIKE 'b'" into {'a': "'b'"}
array.find_all do |item|
terms.all? do |attribute, matcher|
matcher = matcher.gsub('%', '.*') # convert %
matcher = matcher.gsub(/^['"]|["']$/, '') # strip quotes
item.send(attribute) =~ /^#{matcher}$/
end
end
end
# results will be an array holding only the last element in users
results = where(users, "first_name LIKE '%ike%' AND email LIKE '%123%'")
This will only work for where clauses what only contain LIKE statements connected by AND's. Adding support for all valid SQL is left as an exercise for the reader, (or better yet, just left alone).
I've built a ruby gem uber_array to enable sql-like syntax for arrays of Hashes or Objects you might want to try.
require 'uber_array'
# Array of Hash elements with strings as keys
items = [
{ 'name' => 'Jack', 'score' => 999, 'active' => false },
{ 'name' => 'Jake', 'score' => 888, 'active' => true },
{ 'name' => 'John', 'score' => 777, 'active' => true }
]
uber_items = UberArray.new(items)
uber_items.where('name' => 'John')
uber_items.where('name' => /Ja/i)
uber_items.like('ja')
uber_items.where('name' => %w(Dave John Tom))
uber_items.where('score' => 999)
uber_items.where('score' => ->(s){s > 900})
uber_items.where('active' => true, 'score' => 800..900)

Ruby/Rails convert string to a class attribute

Suppose I have a class Article, such that:
class Article
attr_accessor :title, :author
def initialize(title, author)
#title = title
#author= author
end
end
Also, variable atrib is a String containing the name of an attribute. How could I turn this string into a variable to use as a getter?
a = Article.new
atrib='title'
puts a.eval(atrib) # <---- I want to do this
EXTENDED
Suppose I now have an Array of articles, and I want to sort them by title. Is there a way to do the compact version using & as in:
col = Article[0..10]
sorted_one = col.sort_by{|a| a.try('title') } #This works
sorted_two = col.sort_by(&:try('title')) #This does not work
You can use either send or instance_variable_get:
a = Article.new 'Asdf', 'Coco'
a.pubic_send(:title) # (Recommended) Tries to call a public method named 'title'. Can raise NoMethodError
=> "Asdf"
# If at rails like your case:
a.try :title # Tries to call 'title' method, returns `nil` if the receiver is `nil` or it does not respond to method 'title'
=> "Asdf"
a.send(:title) # Same, but will work even if the method is private/protected
=> "Asdf"
a.instance_variable_get :#title # Looks for an instance variable, returns nil if one doesn't exist
=> "Asdf"
Shot answer to your extended question: no. The &:symbol shortcut for procs relies on Symbol#to_proc method. So to enable that behavior you'd need to redifine that method on the Symbol class:
class Symbol
def to_proc
->(x) { x.instance_eval(self.to_s) }
end
end
[1,2,3].map(&:"to_s.to_i * 10")
=> [10, 20, 30]
ActiveRecord instances have an attributes hash:
a = Article.new(title: 'foo')
#=> <#Article id: nil, title: "foo">
atrib = 'title'
a.attributes[atrib]
#=> "foo"
You can use order to get sorted objects from your database:
Article.order('title').first(10)
#=> array of first 10 articles ordered by title

Getting ruby hash values by an array of keys

What I'm aiming to do is to create an object which is initialized with a hash and then query this object in order to get values from that hash.
To make things clearer here's a rough example of what I mean:
class HashHolder
def initialize(hash)
#hash = hash
end
def get_value(*args)
# What are my possibilities here?
end
end
holder = HashHolder.new({:a => { :b => { :c => "value" } } } )
holder.get_value(:a, :b, :c) # should return "value"
I know I can perform iteration on the arguments list as in:
def get_value(*args)
value = #hash
args.each do |k|
value = value[k]
end
return value
end
But if I plan to use this method a lot this is going to degrade my performance dramatically when all I want to do is to access a hash value.
Any suggestions on that?
To update the answer since it's been a while since it was asked.
(tested in ruby 2.3.1)
You have a hash like this:
my_hash = {:a => { :b => { :c => "value" } } }
The question asked:
my_hash.get_value(:a, :b, :c) # should return "value"
Answer: Use 'dig' instead of get_value, like so:
my_hash.dig(:a,:b,:c) # returns "value"
Since the title of the question is misleading (it should be something like: how to get a value inside a nested hash with an array of keys), here is an answer to the question actually asked:
Getting ruby hash values by an array of keys
Preparation:
my_hash = {:a => 1, :b => 3, :d => 6}
my_array = [:a,:d]
Answer:
my_hash.values_at(*my_array) #returns [1,6]
def get_value(*args)
args.inject(#hash, &:fetch)
end
In case you want to avoid iteration at lookup (which I do not feel necessary), then you need to flatten the hash to be stored:
class HashHolder
def initialize(hash)
while hash.values.any?{|v| v.kind_of?(Hash)}
hash.to_a.each{|k, v| if v.kind_of?(Hash); hash.delete(k).each{|kk, vv| hash[[*k, kk]] = vv} end}
end
#hash = hash
end
def get_value(*args)
#hash[args]
end
end
If you know the structure of the hash is always in that format you could just do:
holder[:a][:b][:c]
... returns "value".

Resources