activerecord: check first X chars of column - ruby-on-rails

I'd like to check the first two chars of a number straight in my model. First I define the number of the current logged in user (devise):
user_number = current_user.number.first(2)
then I want to take that value and check it within a where statement in a "number" mobel, so I tried this
#numbers = Number.where(:number_value.first(2) => user_number)
which is obviously the same as
#numbers = Number.where(:number_value.first(2) => current_user.number.first(2))
No, that does not work.
How can I check the first 2 chars of the :number_value column in my model?
Any help is appreciated.
Many thanks.

Solution (SQLite)
#numbers = Number.where("number_value like '" + current_user.number.first(2) + "%'")
since this is not lazy loading I'm not convinced yet that it is the smartest solution. if you know any better, would be cool if you can share

First, you should read the ActiveRecord query guide. I'd also imagine that there's a much more straight forward way for you to accomplish your goal.
But, to answer your specific question, here's an approach that'd work with Postgresql.
Number.where("number_value::text like ?", current_user.number.to_s[0,2] + "%")

Related

Ruby on Rails, sum of an attribute of an attribute

I'm having some problems with my Ruby on Rails website. Let me explain.
I have a user model, it has many credits
In order to count, the credits for a user I do:
#user.credits.sum(:score)
This works fine.
Now have a model team, that has many users, and I want to find out the total number of credits, I found on another StackOverflow post this:
array.inject{|sum,x| sum + x }
So I thought for me it should look like that:
#team.users.inject{|sum,x| sum + x.credits.sum(:score)}
But this returns
#<User:0x00000101a7c180>
instead of the sum. Guess I'm doing something wrong. Don't hesitate if you have an idea.
Thanks
You have to set the initial value:
#team.users.inject(0){ |sum,x| sum + x.credits.sum(:score) }
You could also do:
#team.users.sum{ |x| x.credits.sum(:score) }

Building an ILIKE clause from an array

I'm experimenting with a few concepts (actually playing and learning by building a RoR version of the 1978 database WHATSIT?).
It basically is a has_many :through structure with Subject -> Tags <- Value. I've tried to replicate a little of the command line structure by using a query text field to enter the commands. Basically things like: What's steve's phone.
Anyhow, with that interface most of the searches use ILIKE. I though about enhancing it by allowing OR conditions using some form of an array. Something like What's steve's [son,daugher]. I got it working by creating the ILIKE clause directly, but not with string replacement.
def bracket_to_ilike(arrel,name,bracket)
bracket_array = bracket.match(/\[([^\]]+)\]/)[1].split(',')
like_clause = bracket_array.map {|i| "#{name} ILiKE '#{i}' "}.join(" OR ")
arrel.where(like_clause)
end
bracket_to_ilike(tags,'tags.name','[son,daughter]') produces the like clause tags.name ILiKE 'son' OR tags.name ILiKE 'daughter'
And it get the relations, but with all the talk about using the form ("tags.name ILiKE ? OR tags.name ? ",v1,v2,vN..)., I though I'd ask if anyone has any ideas on how to do that.
Creating variables on the fly is doable from what I've searched, but not in favor. I just wondered if anyone has tried creating a method that can add a where clause that has a variable number parameters.I tried sending the where clause to the relation, but it didn't like that.
Steve
Couple of things to watch out for in your code...
What will happen when one of the elements of bracket_array contains a single quote?
What will happen if I take it step farther and set an element to say "'; drop tables..."?
My first stab at refactoring your code would be to see if Arel can do it. Or Sequeel, or whatever they call the "metawhere" gem these days. My second stab would be something like this:
arrel.where( [ bracket_array.size.times.map{"#{name} ILIKE ?"}.join(' OR '), *bracket_array ])
I didn't test it, but the idea is to use the size of bracket_array to generate a string of OR'd conditions, then use the splat operator to pass in all the values.
Thanks to Phillip for pointing me in the right direction.
I didn't know you could pass an array to a where clause - that opened up some options
I had used the splat operator a few times, but it didn't hit me that it actually creates an object(variable)
The [son,daughter] stuff was just a console exercise to see what I could do, but not sure what I was going to do with it. I ended up taking the model association and creating the array out of the picture and implemented OR searches.
def array_to_ilike(col_name,keys)
ilike = [keys.map {|i| "#{col_name} ILiKE ? "}.join(" OR "), *keys ]
#ilike = [keys.size.times.map{"#{col_name} ILIKE ?"}.join(' OR '), *keys ]
#both work, guess its just what you are use to.
end
I then allowed a pipe(|) character in my subject,tag,values searches, so a WHATSIT style question
What's Steve's Phone Home|Work => displays home and work phone
steve phone home|work The 's stuff is just for show
steve son|daughter => displays children
phone james%|lori% => displays phone number for anyone who's name starts with james or lori
james%|lori% => dumps all information on anyone who's name starts with james or lori
The query then parses the command and if it encounters a | in any of the words, it will do things like:
t_ilike = array_to_ilike('tags.name',name.split("|"))
# or I actually stored it off on the inital parse
t_ilike = #tuple[:tag][:ilike] ||= ['tags.name ilike ?',tag]
Again this is just a learning exercise in creating a non-CRUD class to deal with the parsing and searching.
Steve

Concatenating two fields in a collect

Rails 2.3.5
I'm not having any luck searching for an answer on this. I know I could just write out a manual sql statement with a concat in it, but I thought I'd ask:
To load a select, I'm running a query of shift records. I'm trying to make the value in the select be shift date followed by a space and then the shift name. I can't figure out the syntax for doing a concat of two fields in a collect. The Ruby docs make it looks like plus signs and double quotes should work in a collect but everything I try gets a "expected numeric" error from Rails.
#shift_list = [a find query].collect{|s| [s.shift_date + " " + s.shift_name, s.id]}
Thanks for any help - much appreciated.
Hard to say without knowing what s is going to be or what type s.shift_date and s.shift_name are but maybe you're looking for this:
collect{|s| ["#{s.shift_date} #{s.shift_name}", s.id]}
That is pretty much the same as:
collect{|s| [s.shift_date.to_s + ' ' + s.shift_name.to_s, s.id]}
but less noisy.

Ruby on Rails ActiveRecord: optimizing increment by one

I have an entry ID, which popularity has to be increased by one.
A simple solution looks like this:
Tag.find(id).increment!(:popularity)
However it doesn't seem to be very efficient, because I select the entire entry (*) from the database (even though I don't need it at all) and then do the second query to update it.
Is there a more efficient way to do this? I think, one update statement (without "select") should be enough, but how do I write this?
Tag.increment_counter :popularity, id
Not only does this skip the select, but it increments atomically.
Maybe something like :
Tag.update_all("popularity = popularity + 1", {:id => id})
Or
Tag.where(:id => id).update_all("popularity = popularity + 1")
?

How to sum all properties of a nested collection?

Given I got User.attachments and Attachment.visits as an integer with the number count.
How can I easily count all the visits of all images of that user?
Use ActiveRecord::Base#sum:
user.attachments.sum(:visits)
This should generate an efficient SQL query like this:
SELECT SUM(attachments.visits) FROM attachments WHERE attachments.user_id = ID
user.attachments.map{|a| a.visits}.sum
There's also inject:
user.attachments.inject(0) { |sum, a| sum + a.visits }
People generally (and quite rightly) hate inject, but since the two other main ways of achieving this have been mentioned, I thought I may as well throw it out there. :)
The following works with Plain Old Ruby Objects, and I suspect the following is marginally faster than using count += a.visits, plus it has an emoticon in it:
user.attachments.map(&:visits).inject(:+)

Resources