setting database connection with session variable - ruby-on-rails

I know I am not suppose to do this, but I dont know how else should I do this.
I want to use different database based on which user loged in. So I thought best way would be if I set up a session variable on first user login...
this is how it looks like:
class Stuff < ActiveRecord::Base
establish_connection(
:adapter => "mysql2",
:host => "127.0.0.1",
:username => session["dbuser"],
:password => session["dbuserpass"],
:database => session["dbname"])
and this of course does not work. Does anyone know how to do this?
Thank you.

You can rewrite your method as:
class Stuff < ActiveRecord::Base
def establish_connection_user(user, pass, database)
establish_connection(
:adapter => "mysql2",
:host => "127.0.0.1",
:username => user,
:password => pass,
:database => database)
end
end
and in your controller:
class StuffController < ApplicationController
def login #example
stuff = Stuff.new
stuff.establish_connection_user(
session[:dbuser],
session[:dbuserpass],
session[:dbname])
end
This way you also encapsulate it and make the details less obvious. I suggest you also
encrypt your cookie so you don't have the credentials so exposed. You can take an idea
from this answer:
Storing an encrypted cookie with Rails

You can select the database in your model like this:
establish_connection "db_name_#{session[:something]}"
This way your model know which database to pull/push data.
Look at this: http://m.onkey.org/how-to-access-session-cookies-params-request-in-model

As hinted by kwon in the comments on the original question, I would approach it through using the session only to retain the identity of the user. I would then pull the desired database connection from logic in a model and a central database (the default rails DB) persisting user details and user connection information.
Start with a modification to your user model (assuming that your user has a model that is persisted in a central database)
add an attribute to the user representing the data to be used
in your application controller set the user in a before_filter, based on the session key
initialize your Stuff model with a user argument
You can then lookup your database connection based on the database.yml. Or if you have one database per user and you need this to be dynamic, create a second model (in the central database) representing the database connection with a foreign key onto the user model.
The following is a bunch of code that may or may not work in reality, but hopefully gives you a template for getting started.
class ApplicationController < ActionController::Base
before_filter :set_user
def set_user
begin
#user = UserProfile.find(session[:usernumber]) if session[:usernumber]
rescue
logger.warn "Possible error in set_user. Resetting session: #{$!}"
#user=nil
session[:usernumber]=nil
reset_session
end
end
end
class StuffController < ApplicationController
def show
#stuff = Stuff.user_get(#user, params[:id])
end
end
class Stuff < ActiveRecord::Base
# This would be better moved to a module to reuse across models
def self.establish_connection_user(user)
establish_connection(user.connection_hash)
end
def establish_connection_user(user)
establish_connection(user.connection_hash)
end
def self.user_get user, item_id
establish_connection_user(user)
find(id)
end
def self.user_where user, *query_args
establish_connection_user(user)
where(query_args)
end
# Even better than replicating 'where', create model methods
# that are more representative of your desired functionality
end
class User < ActiveRecord::Base
has_one :user_connection
def connection_hash
uc = self.user_connection
{:database=>uc.db, :password=>uc.pass, :user=>uc.username, :host=>uc.dbhost, :adapter=>uc.adapter}
end
# User probably contains other user-facing details
end

If you have the option of using PostgreSQL you can use the Schemas feature of PostgreSQL to effectively have separate table namespaces (schemas) for each user. The benefit here is you are still connected to the same database (thus avoiding hacking up the rails API), but you get the same benefits of multiple DBs in terms of database separation.
If you have a RailsCasts Pro subscription ($9/mo) Ryan Bates has an excellent video on the subject: http://railscasts.com/episodes/389-multitenancy-with-postgresql
Jerod Santo also did a great write up on his blog: http://blog.jerodsanto.net/2011/07/building-multi-tenant-rails-apps-with-postgresql-schemas/
In both examples they use subdomains to switch between tenants/schemas but you could easily link it to a user record.

Related

Rails STI getting parent class instead of child class

Here is my code:
def data
providers.map do |provider|
binding.pry
[
ERB::Util.h(provider.id),
link_to(raw(image_tag provider.avatar_url(:small), class: 'img-responsive'), admin_provider_path(provider))provider.enabled))
]
end
end
def providers
#providers ||= fetch_providers
end
def fetch_providers
providers = Provider.order("#{sort_column} #{sort_direction}")
providers = providers.page(page).per_page(per_page)
if params[:sSearch].present?
providers = providers.where("email like :search", search: "%#{params[:sSearch]}%")
end
providers
end
My Problem is, I always get nil from provider.avatar_url(:small). After I debug this provider using pry like this:
provider.class.name
=> "User"
I have defined the providers variable to taken from Provider not User
providers = Provider.order("#{sort_column} #{sort_direction}")
I can't get "Provider" class. Which of course carrierwave will search the file using user upload directory not provider upload directory. How do I get this? Thanks in advance
My STI class:
class Provider < User
has_many :products
after_create :assign_default_role
mount_uploader :avatar, AvatarUploader
def self.all
User.with_role(:provider)
end
private
def assign_default_role
self.add_role "provider"
end
end
This will happen if you are using STI without having a type column in your table. When loading record from database, ActiveRecord is using information in that column to determine the class which is to be used to instantiate the object.
To fix, you will need to add the missing column and populate with a Provider for every record representing a subclass.

rails autosave nested model selected with where/find_by

i have a special case for which i need to know the best practice.
Given a simple has_many association:
class Authentication < ActiveRecord::Base
belongs_to :user
#provider can be :password, :facebook_oauth etc
#code is the encrypted password on provider == :password
end
class User < ActiveRecord::Base
has_many :authentications
#this works
def encrypted_password=(pw)
set = false
self.authentications.each do |auth|
if auth.provider.to_sym == :password
set = true
auth.code = pw
end
end
self.authentications.build(provider: :password, code: pw) unless set
pw
end
#this only when no password-auth exist yet
def encrypted_password=(pw)
self.authentications.find_or_initialize_by(provider: :password).code = pw
end
end
and then
user = User.last
user.password="abcdefg"
user.save
While the first solution works, it loads and iterates over ALL associated Authentication objects. It was a workaround but this is a no-go.
The second solution does not work when it loads an existing Password-Authentication object. The User object does not know about the change on the Authentication object loaded with the find_or_initialize_by method. The change won't be saved...
Is there a way to register the changed Authentication object back to the User object so that it will be autosaved when called user.save?
It seems saving associating object returned with find back to parent object is impossible as of now. Refer to this issue https://github.com/rails/rails/issues/17466.
I had the same issue, and my workaround was, even though this is not you nor I wanted, to use save in the method yourself and make all the saves inside the transaction.
def encrypted_password=(pw)
self.authentications.find_or_initialize_by(provider: :password).update_attribute(code, pw)
end
Is there a way to register the changed Authentication object back to the User object so that it will be autosaved when called user.save?
If your question only consists of needing to know how to save an associated class, you can add this to your class definition:
class User < ActiveRecord::Base
has_many :authentications, autosave: true
end
The Authentication object is already referenced back to the User object via the user_id column that should be on Authentication by way of the belongs_to method. This autosave: true will save the associated object Authentication when the parent object (User) is saved.

Creating a Rails change log

I am pretty new to rails (and development) and have a requirement to create a change log. Let's say you have an employees table. On that table you have an employee reference number, a first name, and a last name. When either the first name or last name changes, I need to log it to a table somewhere for later reporting. I only need to log the change, so if employee ref 1 changes from Bill to Bob, then I need to put the reference number and first name into a table. The change table can have all the columns that mnight change, but most only be populated with the reference number and the changed field. I don't need the previous value either, just the new one. hope that makes sense.
Looked at gems such as paper trail, but they seem very complicated for what I need. I don't ever need to manipulate the model or move versions etc, I just need to track which fields have changed, when, and by whom.
I'd appreciate your recommendations.
If you insist on building your own changelog, based on your requirements you can do so using a few callbacks. First create your log table:
def up
create_table :employee_change_logs do |t|
t.references :employee
# as per your spec - copy all column definitions from your employees table
end
end
In your Employee model:
class Employee < ActiveRecord::Base
has_many :employee_change_logs
before_update :capture_changed_columns
after_update :log_changed_columns
# capture the changes before the update occurs
def capture_changed_columns
#changed_columns = changed
end
def log_changed_columns
return if #changed_columns.empty?
log_entry = employee_change_logs.build
#changed_columns.each{|c| log_entry.send(:"#{c}=", self.send(c))}
log_entry.save!
end
end
I recommend the gem vestal_versions.
To version an ActiveRecord model, simply add versioned to your class like so:
class User < ActiveRecord::Base
versioned
validates_presence_of :first_name, :last_name
def name
"#{first_name} #{last_name}"
end
end
And use like this:
#user.update_attributes(:last_name => "Jobs", :updated_by => "Tyler")
#user.version # => 2
#user.versions.last.user # => "Tyler"
The first thing we did was put an around filter in the application controller. This was how I get the current_employee into the employee model, which was the challenge, especially for a newbie like me!
around_filter :set_employee_for_log, :if => Proc.new { #current_account &&
#current_account.log_employee_changes? && #current_employee }
def set_employee_for_log
Thread.current[:current_employee] = #current_employee.id
begin
yield
ensure
Thread.current[:current_employee ] = nil
end
end
end
Next, in the employee model I defined which fields I was interested in monitoring
CHECK_FIELDS = ['first_name', 'last_name', 'middle_name']
then I added some hooks to actually capture the changes IF logging is enabled at the account level
before_update :capture_changed_columns
after_update :log_changed_columns, :if => Proc.new { self.account.log_employee_changes? }
def capture_changed_columns
#changed_columns = changed
#changes = changes
end
def log_changed_columns
e = EmployeeChangeLog.new
Employee::CHECK_FIELDS.each do |field|
if self.send("#{field}_changed?")
e.send("#{field}=", self.send(field))
end
end
if e.changed?
e.update_attribute(:account_id, self.account.id)
e.update_attribute(:employee_id, self.id)
e.update_attribute(:employee_ref, self.employee_ref)
e.update_attribute(:user_id, Thread.current[:current_employee])
e.save
else return
end
end
And that;s it. If the account enables it, the app keeps an eye on specific fields and then all changes to those fields are logged to a table, creating an simple audit trail.

In Rails, is it possible to repair model validation errors inside the model?

Let's say a model catches a validation error, usually this is handled by the controller, but is it possible to handle it automatically by the model?
Practically I want to generate a unique id uid for each Note, the model looks like this:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
end
The closest I got is:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
after_rollback :repair
protected
def repair
if self.errors[:uid].size > 0
self.uid = generate_uid
end
self.save # Try again
end
end
Some immediate problems with my solution: (1) The model instance still has errors that the controller can see, I'm not sure how to clear the errors. (2) The repair method is recursive.
While I'm sure there is a way to catch and handle the errors in the model (maybe the after_validation callback could be of use), perhaps you can avoid the issue in this case by ensuring that the uid you generate is unique when you create it.
Ryan Bates offered this method for generating unique tokens in a RailsCast:
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
With the use of a before_create callback, i.e. before_create { generate_token(:uid) }, each model will have a unique id.
All this said, #Beerlington raises a really good point about UUIDs.
Update: Note that the method given is expecting to be defined in a User model. For your example, you'd want to change it to ...while Note.exists?....
I would use a true UUID that is guaranteed to be unique, and not add the overhead to your model. Having a uniqueness validation in the model adds some overhead because it has to hit the database to figure out if something exists, and it's still not even guaranteed.
Check out this Ruby project to generate UUIDs: https://github.com/assaf/uuid/

Advice on "Dynamic" Model validation

I have a model named Calendar.
The validations that will be applied to it varies from the selections made by the user.
I know that I can use custom validation + conditional validation to do this, but doesn't look very clean to me.
I wonder if I can store it on a database column and pass it to a "generic" validator method.
What do you think?
Explaining further:
A user has a calendar.
Other users that have access to this calendar, can schedule appointments.
To schedule an appointment the app should validate according to the rules defined by the calendar's owner.
There are many combinations, so what I came to is:
Create custom validator classes to each of the possible validations and make then conditional.
class Calendar
validate_allowed_in_hollydays :appointment_date if :allowedinhollydays?
(tenths of other cases)
...
end
This works, but feels wrong.
I'm thinking about storing somewhere which rules should be applied to that calendar and then doing something like:
validate_stored_rules :appointment_date
It seems a little backwards to save the data in the database and then validate it.
I think your initial thought of going with some custom validation is the best bet. validates_with looks like your best option. You could then create a separate class and build all the validation inside that to keep it separate from your main model.
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
class GoodnessValidator < ActiveModel::Validator
def validate
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end
Code lifted straight from the Rails Validation Guide
you should use with_options it allows to put default options into your code:
class User < ActiveRecord::Base
with_options :if => :is_admin do |admin|
admin.validates_length_of :password, :minimum => 10
end
end
in the example is_admin might be an database column, attr_accessor or an method
Thank you all for your help.
I've got it working like this:
def after_initialize
singleton = class << self; self; end
validations = eval(calendar.cofig)
validations.each do |val|
singleton.class_eval(val)
end
end

Resources