How to store find_by_sql_results in session variable - ruby-on-rails

Here is my controller code to check login details of a user
def validateLogin
#email = params[:userEmail1]
#pass = params[:userPassword1]
if params[:userEmail1] != nil
valid_user = Userprofile.find_by_sql(["select * from userprofiles where userEmail=? and userPassword=?", #email, #pass])
if valid_user.count > 0
session[:email] = #email
session[:uid] = valid_user.id
session[:userType] = valid_user.userType # usertype is a column in userprofiles table
# But here i am not receiving the usertype it gives error that undefined variable usertype.
redirect_to "/userhomes/"
else
flash[:message] = "Either email or password is incorrect"
redirect_to '/'
end
else
flash[:message]="Fields can not be blank"
render :action=>'defaults'
end
Please help
session[:userType] = valid_user.userType
# Error: (usertype is a column in userprofiles table)
But here i am not receiving the usertype it gives error that undefined variable usertype.

You are seeing this error because you receive an array of objects from find_by_sql. You even check the size of the array in your if clause.
From your code I think you expect only one returned object. But you still need to get it from the array like so:
profiles = Userprofile.find_by_sql(["select * from userprofiles where userEmail=? and userPassword=?", #email, #pass])
if profiles.count > 0
user_profile = profiles[0]
#... your other stuff
end
Another variant which also much better uses Rails idioms and especially ActiveRecord as is was inteded to be used is to let it construct the SQL by itself which is generally safer, less prone to errors and cacheble.
You didn't write which version of Rails you are using, but for Rails 2.3.x, it looks like this
user_profile = Userprofile.first(:conditions => {:userEmail => #email, :userPassword => #pass})
For Rails 3.x, it looks like this:
user_profile = Userprofile.where(:userEmail => #email, :userPassword => #pass).first
Both variants expect that you have a model called Userprofile, which you generally require to effectively work with database objects in Rails. What both queries do is to create a new model instance from the first row returned from your query (that's what the first does).
Generally, you should get a book or some guide on the internet and learn how to properly use ActivRecord. Note that the API has seriously changed between Rails 2.3 and Rails 3 so make sure to use a guide for your actual Rails version.
And as a final advice, you shouldn't store actual ActiveRecord objects in the session. They would need to be serialized on store and de-serialized on access. What makes it hard (or impossible to track object references.
Also, Rails uses the cookie session store by default, which means that the whole session data is stored in a cookie on the client. The data therein in fully readyabkle to anyone with access to the cookie as it is only signed to restrict tampering with the data, but it is not encrypted. Thus, in your case anyone would be able to ready the (unecrypted) password.
Instead of storing the model object, you should store it's id instead and get the actual (and up-to-date) object from the database instead on each request. This is much easier, saves you from cache inconsistencies (what happens if the user changes her password) and is probably faster than to transfer a huge session cookie from the client on each request.

Related

Ruby on Rails / ActiveRecord: Updating records with first_or_initialize causes RecordNotUnique

I'm currently getting user data from a SAML assertion and creating users in a local DB based on that info:
mapped_role = map_role user_role
user = User.where(email: auth_attrs.single('Email')).first_or_initialize do |u|
u.firstname = auth_attrs.single('First Name')
u.uid = auth_attrs.single('UID')
u.provider = resp.provider
u.role = mapped_role
end
This works well enough, but when the user's details change (for instance, their role changes), that data doesn't get updated in the DB. What I've tried doing is moving the role assignment out of the do block (on the user object returned by first_or_initialize) and then calling a follow-up user.save, but this results in a pretty red screen informing me that the column 'email' isn't unique. I don't want to be creating a new record here, just updating an existing one. Is there a better pattern to be using here?
Edit: I've tried the various approaches laid out here, but they result in the same SQLite3 error. It seems like I'm missing something there.
Edit2: It looks like this might be due to Devise trying to do something behind the scenes with an email field of its own(?).
I think I would go about it like so
mapped_role = map_role user_role
# find the user or initatiate an new un-persisted user
user = User.find_or_initialize_by(email: auth_attrs.single('Email'))
attributes = {firstname: auth_attrs.single('First Name'),
uid: auth_attrs.single('UID'),
provider: resp.provider,
role: mapped_role}
# attempt to update the above record with the appropriate attributes
# this will set the attributes and fire #save
if user.update(attributes)
# successful
else
# handle validation errors
end
This way there is no need for logical handling of users that are already persisted and new users.

Getting data out of devise

I'm migrating away from rails. I will be using the same domain, so I'll get the _session_id cookie that rails uses and I can bring over the old sessions table.I would like to use this to extract data (the user_id) from the old session. I can not tell how to do this outside of rails.
Within a controller there's current_user of course or session["warden.user.user.key"], but how can I take the id, decrypt the data in the table, and pull stuff out on my own (besides running the old rails application and creating a route on that that returns the info I need and hitting it from my new application)?
I'm not entirely sure this is the best way, but I was intrigued so went down the rabbit hole. This works for my 4.1.10 app where sessions are stored in the cookie. You'll want to look at action pack's EncryptedCookieJar class and active support's CachingKeyGenerator and MessageEncryptor classes for details.
Obviously you'll need to replace the two strings that start "THE VALUE…".
key_generator = ActiveSupport::KeyGenerator.new('THE VALUE OF SECRET_KEY_BASE FROM config/secrets.yml', iterations: 1000)
caching_key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
caching_key_generator.generate_key('encrypted cookie')
caching_key_generator.generate_key('signed encrypted cookie')
secret = caching_key_generator.generate_key('encrypted cookie')
sign_secret = caching_key_generator.generate_key('signed encrypted cookie')
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: ActionDispatch::Cookies::NullSerializer)
session_value = CGI::unescape('THE VALUE OF THE SESSION COOKIE')
serialized_result = encryptor.decrypt_and_verify(session_value)
result = Marshal.load(serialized_result)
The result, for me, is a hash that looks exactly the session hash in Rails.
If it doesn't work for you, you may be using a different serializer so need to replace Marshal.load with whatever you need. Just take a look at serialized_result and see.

How checking value from DB table?

I want create a simple checking value from database. Here is my code:
def check_user_name(name, email)
db_name = Customers.find_by_name(name).to_s
db_email = Customers.find_by_email(email).to_s
if name == db_name && email == db_email
return 'yes'
else
return 'no'
end
end
But I have allways 'no' variant....why ?
Because you are calling to_s on your Customers model and not actually getting the name. The two fetch lines you have should be:
Customers.find_by_name(name).name.to_s # to_s probably not necessary if you know this field is a string
Customers.find_by_email(email).email
But, you're making two separate requests to the database. I don't know what the purpose of this is (as you could be selecting two different Customers) but you could do:
if Customers.where(name: name, email: email).exists?
"yes"
else
"no"
end
Since you are, however, selecting by name and email - I would highly recommend that you make sure those fields are indexed because large tables with those requests will bog the server and make that route rather slow (I would actually recommend that you pursue other routes that are more viable, but I wanted to mention this).
When you give Customers.find_by_name(name), you will not get name of a customer. Actually it will return activerecord object, so from this object you need to get the name and email of a customer, like below,
name = Customers.find_by_name(name).name
email = Customers.find_by_email(email).email
Now you will get the exact name and email of matched data from DB.

Rails Devise: How do I (mem)cache devise's database requests for the user object?

Every time I hit an authenticated page, I notice devise issuing an SQL statement :
User Load (0.2ms) SELECT users.* FROM users WHERE (users.id = 1) LIMIT 1
(I'm using Rails 3 btw .. so cache_money seems out as a solution and despite a lot of searching I've found no substitute).
I tried many overrides in the user model and only find_by_sql seems called. Which gets passed a string of the entire SQL statement. Something intuitive like find_by_id or find doesn't seem to get called. I 'can' override this method and glean the user-id and do a reasonable cache system from that - but that's quite ugly.
I also tried overriding authenticate_user which I can intercept one SQL attempt but then calls to current_user seems to try it again.
Simply, my user objects change rarely and its a sad state to keep hitting the db for this instead of a memcache solution. (assume that I'm willing to accept all responsibility for invalidating said cache with :after_save as part but not all of that solution)
The following code will cache the user by its id and
invalidate the cache after each modification.
class User < ActiveRecord::Base
after_save :invalidate_cache
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
user = Rails.cache.fetch("user:#{single_key}") do
User.where(:id => single_key).entries.first
end
# validate user against stored salt in the session
return user if user && user.authenticatable_salt == salt
# fallback to devise default method if user is blank or invalid
super
end
private
def invalidate_cache
Rails.cache.delete("user:#{id}")
end
end
WARNING: There's most likely a better/smarter way to do this.
I chased this problem down a few months back. I found -- or at least, I think I found -- where Devise loads the user object here:
https://github.com/plataformatec/devise/blob/master/lib/devise/rails/warden_compat.rb#L31
I created a monkey patch for that deserialized method in /initializers/warden.rb to do a cache fetch instead of get. It felt dirty and wrong, but it worked.
I've been struggling with this, too.
A less convoluted way of doing this is to add this class method to your User model:
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
Rails.cache.fetch("user:#{single_key}") { User.find(single_key) }
end
Note that I'm prepending the model name to the object ID that is passed in for storing/retrieving the object from the cache; you can use whatever scheme fits your needs.
The only thing to worry about, of course, is invalidating the user in the cache when something changes. It would have been nice instead to store the User in the cache using the session ID as part of the key, but the session is not available in the model class, and is not passed in to this method by Devise.

Rails 3.0.3 Rails.cache.read can't write to DB

I am playing around with the Rails 3 Rails.cache feature and when I write an ActiveRecord entry to cache, I cannot read it back, change the attributes then write to the database. I get a "TypeError: can't modify frozen hash". I used to use another memcache plugin, but I'm trying to switch over to Heroku, and it's incredibly annoying that I cannot save ActiveRecord entries I throw into Memcache. This will result in a lot of unnecessary DB reads to change small bits of information.
For example, I might do this in the database. Assuming the User model is:
User -> login:string and typing the following into rails c
user = User.new
user.login = 'test'
user.save
Rails.cache.write('user:login:test', user)
user2 = Rails.cache.read('user:login:test')
user2.login = 'test2'
TypeError: can't modify frozen hash
/app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/write.rb:26:in `[]='
/app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/write.rb:26:in `write_attribute'
/app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/dirty.rb:61:in `write_attribute'
/app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/write.rb:13:in `login='
Does anyone know how to solve this problem?
After getting the user2 object value from this line:
user2 = Rails.cache.read('user:login:test')
do the following:
dup_user = user2.dup # this will make a clone of the user2 object
dup_user.login = test_2
Then the "frozen hash" exception should not appear again.
When you store an object in cache, the object is frozen. This is one of the reasons why you should never store complex objects, like ActiveRecord models.
When you read from cache, the object is loaded, but it's still frozen. ActiveRecord tries to restore the object state, but it needs to update some internal attributes. The update will fail because the object is frozen.
Instead of storing the entire object, simply store its id and retrieve the user on-the-fly.
user = User.new
user.login = 'test'
user.save
Rails.cache.write('user:login:test', user.id)
user2 = User.find(Rails.cache.read('user:login:test'))
user2.login = 'test2'
This is a little bit less more efficient in terms of caching, but it's the way to go. In fact, you don't consider that in the meanwhile other attributes of the user might have been changed and your object no longer represents the current status.
Also, in this way your cache store won't act as a database and you'll be able to store more cache entries. IDs take less memory than an entire ActiveRecord instance.
Of course, you can use the #dup workaround, but it's just a trick. It's not the solution. You will still have problems with cache integrity as I explained above.
user = User.new
user.login = 'test'
user.save
Rails.cache.write('user:login:test', user)
user2 = Rails.cache.read('user:login:test').dup
user2.login = 'test2'

Resources