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).
Related
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.
I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform
Just having trouble figuring out how to test an action that utilizes a private ApplicationController method. To explain:
A User has and belongs to many Organisations (through Roles), and vice versa. An Organisation has a bunch of related entities, but in the app a user is only dealing with a single Organisation at a time, so for all of my controller methods I want to scope with a current organisation. So in my ApplicationController:
private
# Returns the organisation that is being operated on in this session.
def current_org
# Get the org id from the session. If it doesn't exist, or that org is no longer operable by the current user,
# find an appropriate one and return that.
if (!session[:current_organisation_id] || !current_user.organisations.where(['organisations.id = ?', session[:current_organisation_id]]).first)
# If an org doesn't exist for this user, all hope is lost, just go to the home page
redirect_to '/' if (!current_user.organisations.first)
# Otherwise set the session with the new org
session[:current_organisation_id] = current_user.organisations.first.id;
end
# Return the current org!
current_user.organisations.first
end
First things first, I don't know if this is the best way to scope. I'd love some elegant default_scope thing, but with the use of session variables this seems problematic. Anyway, the above let's me do this in controllers:
class HousesController < ApplicationController
def index
#houses = current_org.houses
end
end
So now I want to use rspec to test my controllers. How can I make sure the current_org method can be called and returns one of the factory_girl Organisation models so that I can write the spec. I'm just confused at how best to bring the factory, spec, action and AppControlled method together.
I am just getting started with Ruby on Rails and i am not sure how i should do the queries to database in order to get data, add data or edit data...
Is it better to find for example a user from the controller or its better to add the queries into the model?
currently, my user homepage controller looks like this with some simple functionality
class HomeController < ApplicationController
#get current_user variable
helper_method :current_user
def index
if user_signed_in?
#user = User.find_by_id(current_user.id)
else
render_404
end
end
end
it simply checks if the user is logged in and finds the user...
Should i move the db calls to a model for best practice or using the above method is also fine?
The way you did it is fine in my opinion. The controller's job is to set the data for the view and part of setting the data is fetching it from the DB.
For more complex DB query you could use scope which are located in the model. This way the controller does not hold too much logic of DB queries on the model.
You usually want to use a repository pattern which rails gives us by default with active record. I'd say that's perfectly fine. If you had more complex business logic or were pulling from multiple tables, then I'd probably move it into it's own class.
I'm trying to add a string to the user model under a location column, based on the user's location. I have everything setup to the point that I know the value of #city+#state is added to the appropriate column in the correct model. The problem is, it appears that request.location.city and request.location.state function properly in the controller and views, but not in the model.
def add_location
#city = request.location.city
#state = request.location.state
#location = #city+#state
self.location = #location
end
When a user is created, rather than creating a string such as "losangelescalifornia", nothing is created. When I define #city = "bob" and #state = "cat", all users created have "bobcat" in the appropriate place. I know then that everything is functioning except these geolocation based methods. So my question is, how would I get these methods (correct me please if that is not what they are) to function in the model, being request.location.city and request.location.state? Many thanks in advance :)
I agree with Rudi's approach, mostly, but I'll offer a little more explanation. The concept you're wrestling with is MVC architecture, which is about separating responsibilities. The models should handle interaction with the DB (or other backend) without needing any knowledge of the context they're being used in (whether it be a an HTTP request or otherwise), views should not need to know about the backend, and controllers handle interactions between the two.
So in the case of your Rails app, the views and controllers have access to the request object, while your models do not. If you want to pass information from the current request to your model, it's up to your controller to do so. I would define your add_location as follows:
class User < ActiveRecord::Base
def add_location(city, state)
self.location = city.to_s + state.to_s # to_s just in case you got nils
end
end
And then in your controller:
class UsersController < ApplicationController
def create # I'm assuming it's create you're dealing with
...
#user.add_location(request.location.city, request.location.state)
...
end
end
I prefer not to pass the request object directly, because that really maintains the separation of the model from the current request. The User model doesn't need to know about request objects or how they work. All it knows is it's getting a city and a state.
Hope that helps.
request variable is not available in the model since it depends on the current HTTP request.
You have to pass to model as param.
def your_action
...
#model.add_location(request)
...
end
def add_location(request)
..
end