Using OR query in rails devise omniauth for multiple users - mysql

I would like to be able to sign a user in with omniauth and devise from multiple accounts. In my user model I have set the twitter user's uid to equal uid and the provider = twitter. For the user's Facebook uid would equal uid2 and provider2 = Facebook.
I currently use this query to either sign a user in if auth.slice = provider, uid or register them if not.
where(auth.slice(:provider, :uid)).first_or_create do |user|
However now I want to run something similar to:
where(auth.slice(:provider, :uid || :provider2, :uid2 )).first_or_create do |user|
I.e where auth.slice = :provider, :uid OR :provider2, :uid2
essentially I need to be able to run an OR query.
Any help will be much appreciated.

Firstly, the auth.slice(:provider, :uid) is referring to keys in the auth hash from omniauth but provider2 and uid2 are only columns in your db, so they can't be used in the slice in any way as they aren't keys in the auth hash (though I realise that was only an illustration/example of yours). You'd need to extract the hash entries and compare with provider and uid columns and provider2 and uid2 columns separately. Also, to do an OR, you could either use the old-school approach of SQL snippets:
Users.where('(provider = :provider AND uid = :uid) OR (provider2 = :provider AND uid2 = :uid', {provider: auth[:provider], uid: auth[:uid]})
or use AREL, as the bog-standard where can't handle OR. The AREL readme is quite readable https://github.com/rails/arel -- you could do something like:
prov = auth[:provider]
uid = auth[:uid]
users = Arel::Table.new(:users)
users.where(users[:provider].eq(prov).and(users[:uid].eq(uid))).or(users[:provider2].eq(prov).and(users[:uid2].eq(uid)))).project(Arel.sql('*'))
However, I think the long-windedness of both of these highlights that the approach of adding sets of columns per-provider makes things a bit of a pain and it would get worse if, for example, you add login via google, yahoo or whatever, resulting in provider3, uid3 and provider4, uid4... An alternative is to have another model/table which stores these different identities, with a has_many relationship with users. For example:
class User < ActiveRecord::Base
has_many :identities
...
end
class Identity < ActiveRecord::Base
belongs_to :user
...
end
Then you could look up by identities using a simple
Identity.where(auth.slice(:provider, :uid))
like you do now, and not have to worry about adding new providers. They would just result in another row in the identies table related to the relevant user. You could join with users or however you want to handle getting the associated user.

Related

Best performance wise query to get parent of queried nested resource

I am implementing an availability model nested within a listing. Its for a rental app.
class Listing
has_many :availabilities, dependent: :destroy
end
class Availability
belongs_to :listing
end
availabilities table has start and end date columns.
I am writing a query through search form to find listings where availabilities are present and the date given in the form lies in between start and end dates fo those availabilities.
My query in a class method looks like:
def self.search(params)
date = params[:date]
listingsids = Availability.where('startdate <= ?', date).where('enddate >= ?', date).pluck('listing_id')
products = Listing.where(id: listingsids)
end
However i feel this is not efficient. I wish I can write Listing.joins(:availability) and then use it but rails won't allow it. I can only join the other way which will give me a relation with availability objects and I want listings i.e. parent resource.
How can I make it more efficient and reduce number of queries I am doing?
Will appreciate your help :)
You should be able to use joins on listing to get you availablity relations, joins works using the relation name, not the model name, so instead of joins(:availability) you should be using joins(:availabilities). Something like this should work and use just a single query for your case:
Listing.joins(:availablities).where('availability.startdate <= ?', date).where('availability.enddate >= ?', date)
notice that joins uses the relation name joins(:availabilities) but the string in the where uses the table name where('availability.startdate <=?', date)

What is the best way to merge 2 tables with Active Record and Mysql

We need to allow users to customize their entities like products... so my intention was to have a product table and a custom_product table with just the information the users are allowed to change.
When a client goes to the product I want to merge the information, means I want to merge the two tables - the custom overwrites the default Products table.
I know that in mysql there exists a ifnull(a.title, b.title) way but I was wondering if there is any nice and efficient way to solve this in Rails 4 with Active Record. Assume that the products and custom products table have just 2 columns, ID and TITLE
I think you can convert both objects to JSON and then handle their params as a hash, using the merge method:
class Product
end
class Customization
belongs_to :product
end
a = Product.find(...)
b = a.customization
c = JSON(a.to_json).merge(JSON(b.to_json).reject!{|k,v| v.nil?})
Therefore c will contain all params from Product eventually overridden by those in Customization which are not nil.
If you still want to use a Product object with hybrid values (taken from Customization) you can try this:
a.attributes = a.attributes.merge(b.attributes.reject!{|k,v| v.nil?})
In this case a will still be a Product instance. I would recommend to keep the same attributes in both models when doing this.

Rails advanced sql query

I have a model User thats has_many user_entitlements. I want to get all of the users that have a preview attribute and no user entitlements.
My code is currently:
User.where("preview is not null").keep_if {|user| user.user_entitlements.empty?}
This is iterating through all of the users and seeing if they have any user entitlements.
Is there a way I could do this in SQL to make it more efficient and avoid having to go through each user?
You can use Arel to build a query leveraging NOT EXISTS.
user_arel = User.arel_table
ue_arel = UserEntitlement.arel_table
User.where("preview is not null").where(
UserEntitlement.where(ue_arel[:user_id].eq(user_arel[:id])).exists.not
)
I am making an assumption on your schema that UserEntitlement contains a user_id column.
Even simpler, you can use includes to force a LEFT JOIN, then check for NULL:
User.includes(:user_entitlements).
where("users.preview IS NOT NULL AND user_entitlements.id IS NULL")

Simple query needs to move to a model

I have a simple mysql query I need to put in my admin. It shows a list of banned customers, as well as their name, notes field, and email. We have queries all over the place in this antiquated rails 2.3 app. Although I'm new to rails, I'm pretty sure this needs to live in the Customer model. I know how to build the table in the view, I'm just not sure on the syntax for the model, should it be a named scope, instance, yata yat ya...any recommendations or help would be more than welcome!
SELECT first_name, last_name, notes, email_primary
FROM customer
WHERE banned = 1
A named scope is appropriate:
class Customer
named_scope :banned, :conditions => {:banned => true}
end
Customer.banned # returns a collection of banned customers

Rails / Active Record has_and_belongs_to_many association - fetching a record

I have two models User and Company associated by has_and_belongs_to_many.
I can fetch all users belonging to a certain company using
Company.find(id).users
The problem I've is finding all users that DO NOT belong to a certain company. Well, I could do that using
User.all - Company.find(id).users
But, I feel there's certainly a better way to achieve this. Is there an activerecord solution to this ?
Currently, I have 8 users (id from 1 to 8). When I try,
User.joins(:companies).where('companies.id = ?', 13).map(&:id)
I get result [7, 8] which is as expected. When I place != in the above query, the results are not what I want, i.e. array of 1 to 6.
Need help.
Thanks.
The easiest way might be with a class method... something like:
class User
has_and_belongs_to_many :companies
class << self
def exclude_company(company)
User.scoped.reject! {|u| u.companies.include? company}
end
end
end
Then in your code:
#company = Company.find(company_id)
#users = User.exclude_company(#company)
YMMV; I've done similar things in the past, but the above code is untested.
Also, if this is a common query pattern, you would probably be better served by the extensions to ARel provided in MetaWhere and its companion MetaSearch. N.B. These are replaced by new gems by the same author in Rails 3.1
Hope this helps.