Flask-Login: how to compare current_user to user id in a relation? - comparison

I'm writing an app with SQLAlchemy backend, that has a model with a field defined as follows:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
class MyObject(db.Model):
user = relationship(User, primaryjoin=user_id==User.id)
Users are logged in by Flask-Login.
In the views I experience somehow peculiar behavior:
36 import ipdb; ipdb.set_trace()
---> 37 if theobject.user != current_user:
38 abort(403)
This always fails with 403.
ipdb> theobject.user
<User emes [emes#test.com]>
ipdb> current_user
<User emes [emes#test.com]>
ipdb> theobject.user == current_user
False
Why does it happen? Is implementing User.__eq__ the proper way to go here?

Can you do comparison based on Ids?
For example
theobject.user.id == current_user.id
# or
theobject.user.email == current_user.email

Related

Model is Invalid Because Foreign Key Does Not Exist When It's Created

I have a user table and 2 other tables that each have foreign keys to the user table's id field. WhenI create a user, I would like to initialize a record in all three tables, but I'm having trouble doing so. The user record is ultimately created, but not the records in the other two tables. After some debugging, I discovered those models were not saved because they were invalid. The error message said, User must exist. I'm trying to accomplish the initialization inside the create method:
def create
logger.info "inside sessions create"
# how do I save user first, THEN create a record associated with user in 2 tables ?
User.where(:id => auth_hash.uid).first_or_create do |user| # finds the matching record in users table
user.name = auth_hash.info.name
user.id = auth_hash.uid
user.token = auth_hash.credentials.token
user.secret = auth_hash.credentials.secret
#tweetstore = Tweetstore.new() # Record I'd like to save in Table with Foreign KEY
#tweetidstore = Tweetidstore.new() # Another Record I'd like to save in a Table with Foreign KEY
istsvalid = #tweetstore.valid? # returns false
istsidvalid = #tweetidstore.valid?
error = #tweetstore.errors.full_messages #USER MUST EXIST
#tweetstore[:user_id] = auth_hash.uid
#tweetidstore[:user_id] = auth_hash.uid
is_tweetstore_save = #tweetstore.save # false
is_tweet_idstore_save = #tweetidstore.save
end
session[:user_id] = auth_hash.uid
redirect_to '/'
end
How do I restructure my code so that User will exist by the time I initialize the other dependant tables? I tried bypassing the problem by adding the optional parameter to the models (eg belongs_to :user, optional: true) but then I get another error: QLite3::ConstraintException: FOREIGN KEY constraint failed I'm a newbie at Ruby so please ELI5
Consider using transactions when you need to create dependent models. When one of the insert fails, let everything that were inserted to DB before that get rollbacked.
Also you should be using user.id and not auth_hash.uid
ex.
#tweetstore[:user_id] = auth_hash.uid
should be
#tweetstore[:user_id] = user.id
Also
istsvalid = #tweetstore.valid? # returns false its false because it's not yet saved into database.
try this:
def create
...
#user.id = auth_hash.uid <---- do not set this
...
#tweetstore[:user_id] = user.id
#tweetidstore[:user_id] = user.id
...
i ditched the first_or_create method and just included a condition for the existence of the record. i'm not sure when user is saved in the where block i used above, so i explicitly saved it before saving records in my dependent tables.
if !User.exists?(auth_hash.uid)
#user = User.new()
#user.name = auth_hash.info.name
#user.id = auth_hash.uid
#user.token = auth_hash.credentials.token
#user.secret = auth_hash.credentials.secret
is_user_save = #user.save
#tweetstore = Tweetstore.new()
#tweetidstore = Tweetidstore.new()
#tweetstore[:user_id] = auth_hash.uid
#tweetidstore[:user_id] = auth_hash.uid
is_tweetstore_save = #tweetstore.save
is_tweet_idstore_save = #tweetidstore.save
end

Rails Facebook Omniauth get user address

I'm tryig to get the user address from facebook with Omniauth but did not work.
i added their address on update callback after login.
If i removed their address from omniauth the app did not update their address.
Someone have any idea how to get their address and why the app did not edit and update their address after the login?
thank's
def omniauth_callback
auth_hash = request.env['omniauth.auth']
user = User.find_by_uid(auth_hash[:uid])
if user.nil? && auth_hash[:info][:email]
user = User.find_by_email(auth_hash[:info][:email])
end
if user.nil?
email_domain = ''
email_domain = '#facebook.com' if auth_hash[:provider] == 'facebook'
user = User.new(email: auth_hash[:info][:email] || auth_hash[:info][:nickname] + email_domain, name: auth_hash[:info][:first_name] || '', surname: auth_hash[:info][:last_name] || '', gender: 'I')
user.password_digest = ''
user.save!(validate: false)
end
user.update_attribute(:login_at, Time.zone.now)
user.update_attribute(:address)
user.update_attribute(:neighborhood)
user.update_attribute(:postal_code)
user.update_attribute(:ip_address, request.remote_ip)
user.update_attribute(:provider, auth_hash[:provider])
user.update_attribute(:uid, auth_hash[:uid])
user.update_attribute(:oauth_token, auth_hash[:credentials][:token])
user.update_attribute(:oauth_expires_at, Time.at(auth_hash[:credentials][:expires_at]))
cookies[:auth_token] = { value: user.oauth_token, expires: user.oauth_expires_at}
redirect_to root_url
end
One reason your code will not work is because this
user.update_attribute(:address)
Doesn't do anything - except raise an error. You have to pass a value into update_attribute as well as specify the field.
Also as #mysmallidea points out, you'd be better advised to use update as that will allow you to update multiple fields in one database action.
If present, the address data will be within the auth_hash. So I suggest that you first work out the structure of that hash. In your development environment, add the following:
Rails.logger.info auth_hash.inspect
That will output the current auth_hash to logs/development.log. Use that to determine where in the hash the address data is. You'll then be able to do something like:
user.update address: auth_hash[:info][:address]
However, you may find that the address is not included in the data returned by the facebook oauth system. In which case you will need to return to their documentation to see if this is possible.

Rails OR condition inside where with same input

I have the following active record query:
User.where(["id = ? OR token = ?", params[:id], params[:id]]).first
Here, params[:id] = 9MrEflgr
PROBLEM
As per logic, params[:id] can be numeric id or alphanumeric token.
I want to get something like User.find_by_id_or_token(params[:id]) in where clause.
Here, since the params[:id] starts with '9', so active record gives me user with id 9 instead of checking for token field. How to avoid this?
As the comment mentioned, you need to check if the params is an integer. This SO question has good suggestions on how to do that (where you can implement is_integer? below).
if params[:id].is_integer?
User.where(["id = ? OR token = ?", params[:id], params[:id]]).first
else
User.where(["token = ?", params[:id]]).first
end

Ruby on Rails: See if a record exists that matches query on multiple columns

I'm developing an app that allows a user to answer a survey once, when they answer it (the first time) I'm capturing the users ip address and user agent (in the Test_users model).
What I'm trying to do is if the user navigates to the survey again they are redirected to a page telling them that they can only complete it once.
In the test controller, I'm thinking this should be:
def new
#Find the Test
#test = Test.find(params[:test_id])
if [[a record in test_users exists with the test id / ip address / user agent]]
redirect_to already_completed_path
end
end
I'm struggling with what this if statement, any help would be greatly appreciated
You can use the exists? method if you don't need the object for something else:
if Test.exists?(:test_id => params[:test_id], :ip_address => request.remote_ip, :user_agent => request.env['HTTP_USER_AGENT'])
redirect_to already_completed_path
end
Use the where clause and put all your conditions in it:
#test = Test.where("test_id = ? or ip_address = ? or user_agent = ?",
test_id, ip_address, user_agent)
This should work, assuming you are storing request.remote_ip and request.env['HTTP_USER_AGENT'] values in your tests table:
if #test.where(ip: request.remote_ip, user_agent: request.env['HTTP_USER_AGENT']).exists?
redirect_to already_completed_path
end
You Should be doing like this
In your Test Controller
def new
#Find the Test
#test = Test.find(params[:id]) #it should be `:id` not `:test_id` because the model wont be having its own foreign key in its own record
#test_user = TestUser.where("test_id = ? or ip_address = ? or user_agent = ?",
test_id, ip_address, user_agent)
if #test_user.exists?
redirect_to already_completed_path
else
redirect_to new_path

How to store the result of my algorithm?

I have an algorithm that searches through all of my sites users, finding those which share a common property with the user using the algorithm (by going to a certain page). It can find multiple users, each can have multiple shared properties. The algorithm works fine, in terms of finding the matches, but I'm having trouble working out how to store the data so that later I'll be able to use each unit of information. I need to be able to access both the found users, and each of the respective shared properties, so I can't just build a string. This is an example of the output, being run from the perspective of user 1:
user 4
sharedproperty3
sharedproperty6
user 6
sharedproperty6
sharedproperty10
shareproperty11
What do I need to do to be able to store this data, and have access to any bit of it for further manipulation? I was thinking of a hash of a hash, but I can't really wrap my head around it. I'm pretty new to programming, and Ruby in particular. Thanks for reading!
EDIT - Here's the code. I'm fully expecting this to be the most incorrect way to do this, but it's my first try so be gentle :)
So if I'm understanding you guys correctly, instead of adding the interests to a string, I should be creating an array or a hash, adding each interest as I find it, then storing each of these in an array or hash? Thanks so much for the help.
def getMatchedUsers
matched_user_html = nil
combined_properties = nil
online_user_list = User.logged_in.all
shared_interest = false
online_user_list.each do |n| # for every online user
combined_properties = nil
if n.email != current_user.email # that is not the current user
current_user.properties.each do |o| # go through all of the current users properties
n.properties.each do |p| # go through the online users properties
if p.interestname.eql?(o.interestname) # if the online users property matches the current user
shared_interest = true
if combined_properties == nil
combined_properties = o.interestname
else
combined_properties = combined_properties + ", " + o.interestname
end
end
end
if shared_interest == true
matched_user_html = n.actualname + ": " + combined_properties
end
end
end
end
return matched_user_html
render :nothing => true
end
This returns an array of hashes with all users and their corresponding sharedproperties.
class User
def find_matching_users
returning Array.new do |matching_users|
self.logged_in.each do |other_user|
next if current_user == other_user # jump if current_user
# see http://ruby-doc.org/core/classes/Array.html#M002212 for more details on the & opreator
unless (common_properties = current_user.properties & other_user.properties).empty?
matching_users << { :user => other_user, :common_properties => common_properties }
end
end
end
end
end
In your view you can do something like this:
<%- current_user.find_matching_users.each do |matching_user| -%>
<%-# you can acccess the user with matching_user[:user] -%>
<%-# you can acccess the common properties with matching_user[:common_properties] -%>
<%- end -%>
You can use a hash table with the key being the user object and the value being an array of the shared properties . This is assuming that you first need to do a lookup based on the user .
Something like this :
#user_results = { user1 => [sharedproperty3,sharedproperty7] , user2 => [sharedproperty10,sharedproperty11,sharedproperty12]}
You can then acces the values like :
#user_results[user1]
or you can also iterate over all the keys using #user_results.keys

Resources