I have two websites that each connect to their own unique databases. I need to validate in website 'A' that an email address exists in the website 'B' database. I'm doing the validation as follows:
Called from website 'A's AccountController < ApplicationController class:
config = YAML::load(File.open("#{RAILS_ROOT}/config/database.yml"))
ActiveRecord::Base.establish_connection(config["database B"])
if ActiveRecord::Base.connection.select_values("SELECT "database B".X
FROM 'database B".X WHERE 'database B'.X.email = #member_email")
This call works when I test it in my development and QA environments but fails in my production environment. What appears to happen in production is that the value of the ActiveRecord and also the select get's co-mingled with currently logged-in user's active records, but only in production.
Okay so I've modified my files to the following, based on the feedback. Still not working... Could someone please review the files below and see what step(s) I'm missing? Thanks!
Thanks! I think that is what I did, but I created the 'model', and, being a newbie, I'm not sure if that would normally be generated by Rails...
Still failing, would you mind taking a look at the following and see if you see what I'm doing wrong?
First, this is the 'legacy' database model for the second database that I want to connect to in the existing application (Note that doing the 'Fileload' was the only way I could get this to not error out.):
class MMSDB < ActiveRecord::Base
self.abstract_class = true #important!
config = YAML::load(File.open("#{RAILS_ROOT}/config/database.yml"))
establish_connection(config["deals_qa"])
end
Second, this is the model that calls the 'MMSDB' Model (see above)
class Redirect < MMSDB
def initialize
end
Checking to see if the email address exists in the legacy database, and, if it does, the #increment the redirect count on the # database table 'members'
Do I somehow need to tell the application what table I want to pull from since the table # in the legacy database (members) would be different then in the current application #database (users)
def email_exists?(email)
if find_by_email(email)
user = find_by_email(email)
user.redirect_count += 1
user.save
end
end
end
Then this is the code snippet inside the account controller file.
else
if user == Redirect::User.email_exists?(#email)
#Redirect_Flag = true
else
flash.now[:error] = 'Invalid email or password; please try again.'
end
end
Subclassing ActiveRecord::Base will allow you to make multiple connections to different databases.
module DatabaseB
class Base < ActiveRecord::Base
#abstract_class = true
establish_connection(config["database B"])
end
end
class YourAwesomeModel < DatabaseB::Base
set_table_name "X"
# Use your regular active record magic
end
You will still be able to use your other models with the connection established using ActiveRecord::Base to your primary database.
Related
I'm currently trying to automatically create an user_address (which will be a randomly generated hash, which is for now hardcoded) string upon sign-up with the Ruby on Rails devise Gem. I have added the user_address to the list of allowed parameters I am currently trying to add this logic to the registrations_controller.rb by adding the following method :
def create
super do
current_user.user_address = '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX'
end
end
I suppose this is because current_user must not be defined before the actual POST (create user) has been processed but I am not sure about the way to do this.
Any help would be greatly appreciated, thanks in advance
If i understand you correctly (I think I do) you could move away from trying to do this in the create action in the controller and instead use a model callback on the User model.. that way its automatically created when a user registers.
example:
class User < ApplicationRecord
before_create :assign_user_address
validates_uniqueness_of :user_address
def assign_user_address
begin
self.user_address = SecureRandom.hex
other_user = User.find_by(user_address: self.user_address)
end while other_user
end
end
the before_create will generate the user_address and assign it to the user that is registering, while the validates_uniqueness_of ensures that you will never have a duplicate user address all in one fell swoop (although with a random string the chances of duplicating are slim.. but better to be safe than sorry.. I find this method super easy and keeps your controller clean and un cluttered.
Let me know if this wasn't what you were looking for..
I have a multi-domain Rails 4 app where the request.domain of the http request determines which functionality I expose a given visitor to. A visitor can sign up through Devise. During the user creation a user.domain field will be filled in with the domain he signed up for, and he will then only have access to this particular domain and its functionality.
Question:
Each domain in my app should be served by its own MongoDB database. If user.domain == 'domain1.com' the user object, as well as all objects that belongs_to it, should be stored in the database that serves domain1.com. How do I set this up?
Complication 1:
I do not only interact with my database through Mongoid, but also through mongo Shell methods like db.collection.insert(). I need to have the correct database connection in both cases. Example:
class ApplicationController < ActionController::Base
before_filter :connect_to_db
def connect_to_db
domain = request.domain
# Establish connection
end
end
Complication 2:
A lot of the database interaction happens outside the MVC context. I need to have the correct database connection in e.g. a Sidekiq context. Example:
class MyJob
include Sidekiq::Worker
def perform(domain)
connect_to_db(domain)
User.all.each do |user|
...
end
end
def connect_to_db(domain)
# Establish connection
end
end
Potentially relevant SO answers:
This answer and this answer suggests that you can apply a set_database or store_in session method on a model level, but this would not be sufficient for me, because models are shared between my domains. Various stragegies have also been discussed in this Google group.
Our product is a Rails application; authentication is handled with Devise and OmniAuth. ~2000 users total. We've recently had reports of some users not being able to sign in, but we can't figure out why. Not getting any server errors or anything in our production logs to suggest anything is awry.
Let's look at some code…
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
...
def twitter
oauthorize "twitter"
end
private
def oauthorize(provider)
if env['omniauth.auth']
#identity = Identity.from_omniauth(env['omniauth.auth'])
#person = #identity.person
# 1. failing here? Maybe?
if #person
PersonMeta.create_for_person(#person, session[:referrer], session[:landing_page]) if #person.first_visit?
# 2. PersonMetas *aren't* being created.
flash[:notice] = I18n.t("devise.omniauth_callbacks.success", kind: provider)
sign_in_and_redirect(#person, :event => :authentication)
# 3. Definitely failing by here…
else
redirect_to root_url
end
else
redirect_to root_url
end
end
end
class Identity < ActiveRecord::Base
belongs_to :person, counter_cache: true, touch: true
after_create :check_person
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first_or_initialize.tap do |identity|
identity.oauth_token = auth['credentials']['token']
identity.oauth_secret = auth['credentials']['secret']
case auth['provider']
when "twitter"
identity.name = auth['info']['name']
identity.nickname = auth['info']['nickname']
identity.bio = auth['info']['description']
identity.avatar_address = auth['info']['image']
else
raise "Provider #{provider} not handled"
end
identity.save
end
end
def check_person
if person_id.nil?
p = create_person(nickname: nickname, name: name, remote_avatar_url: biggest_avatar)
p.identities << self
end
end
def biggest_avatar
avatar_address.gsub('_bigger', '').gsub('_normal', '') if avatar_address
end
end
class PersonMeta < ActiveRecord::Base
attr_accessible :landing_page, :mixpanel_id, :referrer_url, :person_id
belongs_to :person
def self.create_for_person(person, referrer, landing_page)
PersonMeta.create!(referrer_url: referrer, landing_page: landing_page, person_id: person.id)
end
end
So we have that, and we're not getting any errors in production.
Where do we start? Well, let's see if the point of failure is Identity.from_omniauth
This method searches for an existing identity (we've written extra code for more providers, but not implemented client-side yet). If no identity is found it will create one, and then create the associated Person model. If this was the point of failure we'd be able to see some suspiciously empty fields in the production console. But no - the Person & Identity models have all been created with all of the correct fields, and the relevant bits of the app have seen them (e.g. their 'user profile pages' have all been created).
I just added in the if #person to the #oauthorize - we had one 500 where #identity.person was nil, but haven't been able to replicate.
Anyway, the real-world users in question do have complete models with associations intact. Moving down the method we then create a PersonMeta record to record simple stuff like landing page. I'd have done this as an after_create on the Person but I figured it wasn't right to be passing session data to a model.
This isn't being created for our problematic users. At this point, I'm kind of stumped. I'm not sure how the create ! (with bang) got in there, but shouldn't this be throwing an exception if somthing's broken? It isn't.
That is only called if it's a person's first visit anyway - subsequent logins should bypass it. One of the problematic users is a friend so I've been getting him to try out various other things, including signing in again, trying different browsers etc, and it keeps happening
so anyway, after spending 45 minutes writing this post…
One of the users revoked access to the app via Twitter and reauthenticated. Everything works now.
What the hell?
His old identity had his OAuth tokens etc stored properly.
Luckily this is resolved for one user but it's obviously an ongoing problem.
What do we do?
Is it possible that the identity.save line in Identity.from_omniauth is failing silently? If so, your after_create hook won't run, #identity.person will be nil, and you'll just (silently) redirect.
Try identity.save! ?
I'm building an application that will require CouchDB's mobile syncing feature.
So for each 'account' on the service I want to create a separate CouchDB database instance so that only this account's data is synced.
I'm using CouchRest Model and Devise, which handles subdomain authentication via a separate users database.
However what is the correct way to connect to the appropriate database at runtime for each model?
A before_filter that sets up a named connection, then loops through each model and does something like this: ?
[Post, Tag, Comment].each do |model|
model_server = CouchRest::Server.new(couch_config[:connection])
model_server.default_database = "my_project-#{Rails.env}-#{model.to_s.downcase}"
model.database = model_server.default_database
end
(Pseudocode)
Assuming that the web server (Heroku) runs each request in a separate thread, this should mean that on each request, the database connection is changed dynamically.
Seems like there should be an easier way!
As a solution to question you can override the database method:
class OneDbPerAccountDocument < CouchRest::ExtendedDocument
def self.database
Account.current_database
end
...
end
And then just subclass your models (Post, Tag, Comment) from this class.
class Account < OneDbPerAccountDocument
def self.current=(name)
#current_database = #couch_server.database("my-project_#{name}")
end
def self.current_database
#current_database
end
end
With this trick all you need to do in controller is just call something like
Account.current = request.subdomain
But, beware that this approach will become a little messy when you'll have several thousands of accounts (databases).
I'm shifting code from an application built in a non-standard custom PHP framework into Ruby on Rails (version 3). In the PHP version all the controllers are really fat, with thin models, which I've always disagreed with, so I'm enjoying the way Rails does validation at the model level, which is probably 90% of what's happening in these fat controllers currently.
One problem I'm facing, and unsure how to resolve however, is that of differing validation rules based on who's making the change to the model. For example, an administrator, or the original creator of the record should be able to do things like flag a record as deleted (soft delete) whereas everybody else should not.
class Something < ActiveRecord::Base
...
validates :deleted, :owned_by_active_user => true
...
end
class OwnedByActiveUserValidator < ActiveModel::EachValidator
validate_each(record, attr_name, attr_value)
# Bad idea to have the model know about things such as sessions?
unless active_user.admin? || active_user.own?(record)
record.errors.add :base, "You do not have permission to delete this record"
end
end
end
Since the model itself is (in theory) unaware of the user who is making the change, what's the "rails way" to do this sort of thing? Should I set the active user as a virtual attribute on the record (not actually saved to DB), or should I just perform these checks in the controller? I have to admit, it does feel strange to have the model checking permissions on the active user, and it adds complexity when it comes to testing the model.
One reason I'm keen to keep as much of this as possible in the model, is because I want to provide both an API (accessed over OAuth) and a web site, without duplicating too much code, such as these types of permissions checks.
It is really the controller's job to handle authorization, or to delegate authorization to an authorization layer. The models should not know about, nor have to care about, who is currently logged in and what his/her permissions are - that's the job of the controller, or whatever auth helper layer the controller delegates that to.
You should make :deleted in-attr_accessible to mass assignment via new, create, or update_attributes. The controller should check the authenticated user's authorizations separately and call deleted= separately, if the authenticated user is authorized.
There are several authorization libraries and frameworks to help with authorization or to function as an authorization layer, such as cancan.
I would solve this with a before_filter in my controller, instead of with validations in my model.
class SomethingController < ApplicationController
before_filter :require_delete_permission, :only => [:destroy]
def destroy
# delete the record
end
private
def require_delete_permission
unless current_user.is_admin || record.owner == current_user
flash[:error] = 'You do not have delete permissions'
redirect_to somewhere
end
end
end
I have come across the same issue in Rails 2.3 and finally come up with this solution. In your model you define some atribute, depending on which you switch on/off validation. Than you your control you set this attribute depending on the date available to controller (such as user privileges in your case) as follows:
Class Model < ActiveRecord::Base
attr_accessor :perform_validation_of_field1 #This is an attribute which controller will use to turn on/off some validation logic depending on the current user
validates_presence_of :field1, :if => :perform_validation_of_field1
#This validation (or any similar one) will occur only if controller sets model.perform_validation_of_field1 to true.
end
Class MyController < ActionController::Base
def update
#item = Model.find(params[:id])
#item.update_attribute(params[:item])
#The controller decides whether to turn on optional validations depending on current user privileges (without the knowledge of internal implementation of this validation logic)
#item.perform_validation_of_field1 = true unless active_user.admin?
if #item.save
flash[:success] = 'The record has been saved'
redirect_to ...
else
flash.now[:error] = 'The record has not passed validation checks'
render :action => :edit
end
end
I think that in Rails 3 it can be done in similar manner.