Ruby datamapper - search in flag value - sql

I have a user table (datamapper model) that has a column called permission which contains the bitmask value.
property :permission, Flag[:perm1, :perm2, :perm3]
I want to find all the users who have certain permissions, such as perm1 and perm2
so I call,
User.all(:permission => [:perm1, :perm2])
This makes query
select * from user where permission = 3 that is incorrect.
while correct query should have been (because it is type - flag)
select * from user where permission &1 != 0 and permission &2 != 0
Does anyone in ruby datamapper, how to make the call to search in flag values.

I could not find any direct way to do it. So did use this hack.
User.all(:conditions => ['permission & ? != 0 and permission & ? != 0', 1,2])

Which version are you running? Under 1.2, I get SELECT ... FROM "users" WHERE "permission" IN (1, 2) ... with User.all(:permission => [:perm1, :perm2]).
One option is to make a union: User.all(:permission => :perm1) | User.all(:permission => :perm2).
Or maybe shortened to User.perm1s | User.perm2s by class methods:
class User
# ...
def self.perm1s; all :permission => :perm1 end
def self.perm2s; all :permission => :perm2 end
end
Not exactly the same query with either one as you've shown, but the result should be the same.

Related

SQL injections in Rails 4 issue

I'm trying to learn about SQL injections and have tried to implement these, but when I put this code in my controller:
params[:username] = "johndoe') OR admin = 't' --"
#user_query = User.find(:first, :conditions => "username = '#{params[:username]}'")
I get the following error:
Couldn't find all Users with 'id': (first, {:conditions=>"username = 'johndoe') OR admin = 't' --'"}) (found 0 results, but was looking for 2)
I have created a User Model with the username "johndoe", but I am still getting no proper response. BTW I am using Rails 4.
You're using an ancient Rails syntax. Don't use
find(:first, :condition => <condition>) ...
Instead use
User.where(<condtion>).first
find accepts a list of IDs to lookup records for. You're giving it an ID of :first and an ID of condition: ..., which aren't going to match any records.
User.where(attr1: value, attr2: value2)
or for single items
User.find_by(attr1: value, attr2: value)
Bear in mind that while doing all this, it would be valuable to check what the actual sql statement is by adding "to_sql" to the end of the query method (From what I remember, find_by just does a LIMIT by 1)

How to insert custom value after validation in rails model

This has been really difficult to find information on. The crux of it all is that I've got a Rails 3.2 app that accesses a MySQL database table with a column of type POINT. Without non-native code, rails doesn't know how to interpret this, which is fine because I only use it in internal DB queries.
The problem, however, is that it gets cast as an integer, and forced to null if blank. MySQL doesn't allow null for this field because there's an index on it, and integers are invalid, so this effectively means that I can't create new records through rails.
I've been searching for a way to change the value just before insertion into the db, but I'm just not up enough on my rails lit to pull it off. So far I've tried the following:
...
after_validation :set_geopoint_blank
def set_geopoint_blank
raw_write_attribute(:geopoint, '') if geopoint.blank?
#this results in NULL value in INSERT statement
end
---------------------------
#thing_controller.rb
...
def create
#thing = Thing.new
#thing.geopoint = 'GeomFromText("POINT(' + lat + ' ' + lng + ')")'
#thing.save
# This also results in NULL and an error
end
---------------------------
#thing_controller.rb
...
def create
#thing = Thing.new
#thing.geopoint = '1'
#thing.save
# This results in `1` being inserted, but fails because that's invalid spatial data.
end
To me, the ideal would be to be able to force rails to put the string 'GeomFromText(...)' into the insert statement that it creates, but I don't know how to do that.
Awaiting the thoughts and opinions of the all-knowing community....
Ok, I ended up using the first link in steve klein's comment to just insert raw sql. Here's what my code looks like in the end:
def create
# Create a Thing instance and assign it the POSTed values
#thing = Thing.new
#thing.assign_attributes(params[:thing], :as => :admin)
# Check to see if all the passed values are valid
if #thing.valid?
# If so, start a DB transaction
ActiveRecord::Base.transaction do
# Insert the minimum data, plus the geopoint
sql = 'INSERT INTO `things`
(`thing_name`,`thing_location`,`geopoint`)
values (
"tmp_insert",
"tmp_location",
GeomFromText("POINT(' + params[:thing][:lat].to_f.to_s + ' ' + params[:thing][:lng].to_f.to_s + ')")
)'
id = ActiveRecord::Base.connection.insert(sql)
# Then load in the newly-created Thing instance and update it's values with the passed values
#real_thing = Thing.find(id)
#real_thing.update_attributes(b, :as => :admin)
end
# Notify the user of success
flash[:message] = { :header => 'Thing successfully created!' }
redirect_to edit_admin_thing_path(#real_thing)
else
# If passed values not valid, alert and re-render form
flash[:error] = { :header => 'Oops! You\'ve got some errors:', :body => #thing.errors.full_messages.join("</p><p>").html_safe }
render 'admin/things/new'
end
end
Not beautiful, but it works.

Find all matching criteria where conditions not in array

I am trying to return a variable with values of an array of users. There are a few conditions that must be met. The user must have public_find set to true, it must not be the current_user (session variable of the user currently logged in), and it must not already be part of a friendship. The first and second conditions work perfectly. However, I am having an issue with the third part where the current_users.friendships needs to be an array of the ID values of the users where the association already exists. Any thoughts?
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id, current_user.friendships])
Edit:
I've figured out that I was missing pluck from the list. This works good now. However, if someone does not yet have a friend then current_user.friendships.pluck(:friend_id) will return NULL. I know that it is bad practice and unexpected results returned when using NOT IN and NULL. However, how do you create a condition where you can set the value to something realistic like [0] or [1] if the array returned is empty?
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id, current_user.friendships.pluck(:friend_id) || [0]])
EDIT AGAIN:
I got it working. However, now I want to know if this is best practice to have a statement like this. It basically is doing a check to see if the current_user.friendships.pluck(:friend_id) is empty or not. If it is then return [0]. Otherwise return an array of the user ids (foreign keyed as friend_id).
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id,
(current_user.friendships.pluck(:friend_id).empty? ? [0] : current_user.friendships.pluck(:friend_id))])
You can write this a little nicer ..
Show all users where public_find is true and also exclude the currently logged in user or any of their friends
ids = current_user.friendships.map(&:friend_id).concat([current_user.id])
#users = User.where(:public_find => true).where('id not in ?', ids)
I would use an arel table for this (which guarantees the code will work on any database):
t, f = User.arel_table, current_user.friendships
query = t[:public_find].eq(true).and(t[:id].not_eq(current_user.id))
query = query.and(t[:id].not_in(f.pluck(:friend_id))) unless f.empty?
#users = User.where(query)
Generated SQL for current_user = 3 and a single friendship with a user with id = 1:
SELECT "users".* FROM "users"
WHERE ("users"."public_find" = 't' AND "users"."id" != 3 AND "users"."id" NOT IN (1))
If current_user.friendships is nil, the unless f.empty? clause will prevent that condition from being applied at all, so it will not appear in the SQL:
SELECT "users".* FROM "users"
WHERE ("users"."public_find" = 't' AND "users"."id" != 3)
Also, note that because this code uses where instead of find, the final result is an ActiveRecord::Relation rather than an array of results. This means that you can further chain conditions onto it, e.g. to order the results by updated_at, change the last line to:
#users = User.where(query).order(:created_at)

Querying distinct with MongoMapper

How do I query distinct with MongoMapper? My query is:
subscribedToThread = Comment.where(:subscribe_thread => 1).all
But this will return many objects with the same user_id. I need to return just a distinct user_id. Is this possible?
I think you will need to drop down to the ruby driver in order to do this as I don't think you can do this with MongoMapper itself:
subscribedToThread = Comment.collection.distinct("user_id", {:subscribe_thread => 1})
Calling the collection method on a model returns the collection as would be provided by the Ruby driver directly so you can issue a distinct query using the syntax below:
collection.distinct(key, query = nil)
You can read more about it here
Yes, you can do so:
subscribedToThread = Comment.where(:subscribe_thread => 1).fields(:user_id).all.compact!.unique!
This will nil every field but user_id which you then uniq!,ie you remove all doubles and then compact! all nil
http://mongomapper.com/documentation/plugins/querying.html#fields
Try this
subscribedToThread = Comment.where(:subscribe_thread => 1).fields(:user_id).collect(&:user_id).uniq
It will show you list of uniq user_id

rails 3 where statement

In rails 3, the where statement of active record returns an active record object. i.e it uses lazy loading like
cars = Car.where(:colour => 'black') # No Query
cars.each {|c| puts c.name } # Fires "select * from cars where ..."
but when I fires,
cars = Car.where(:colour => 'black')
in console, it returns the result without this lazy loading why ?
Your console implicitly calls inspect on the result of your expression, which triggers the query.
You can avoid the inspection by appending a semicolon:
cars = Car.where(:colour => 'black');