Skip to content

Commit

Permalink
Publish version 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ZenCocoon committed Nov 11, 2009
1 parent c9043d2 commit b48f161
Show file tree
Hide file tree
Showing 18 changed files with 326 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ rdoc
pkg

## PROJECT::SPECIFIC
*.log
57 changes: 55 additions & 2 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,59 @@
= account_scopper
= Account Scopper

Description goes here.
Account Scopper aims to make conversion from a single account to multi-accounts application as easy as possible.

Simply set your current_account before each request and the scoping will be done on it's own.

== Usage

To convert your application, you will need to make few changes :

1. Create an Account model and Accounts table
2. Add account_id to your existing models (except Account of course)
3. Make relationship between Account and other models
4. Add the class variable "current_account" to the model Account
5. Initialize Account.current_account before every request (eg: app/controllers/application.rb)

eg:

# app/model/account.rb

class Account < ActiveRecord::Base
cattr_accessor :current_account

has_many :users
end

# app/model/user.rb

class User < ActiveRecord::Base
belongs_to :account
end

# app/controllers/application.rb

class ApplicationController < ActionController::Base
before_filter :set_current_account
# ...

private
# Don't forget that @current_account should be properly setup to be an Account object
# for this purpose you can use the plugin AccountLocation from David Heinemeier Hansson
def set_current_account
Account.current_account = @current_account
end
end

== What it does

The plugin overwrite few methods from ActiveRecord::Base

To have a better understanding of what it does, the best way is still to look
at the code itself and the tests.

== Warning

If using manually generated database requests like find_by_sql, ... you'll have to do your own scoping

== Note on Patches/Pull Requests

Expand Down
4 changes: 2 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "account_scopper"
gem.summary = %Q{TODO: one-line summary of your gem}
gem.description = %Q{TODO: longer description of your gem}
gem.summary = "Account Scopper: Automatically scope your ActiveRecord's model by account. Ideal for multi-account applications."
gem.description = "Account Scopper: Automatically scope your ActiveRecord's model by account. Ideal for multi-account applications."
gem.email = "public@zencocoon.com"
gem.homepage = "http://github.com/ZenCocoon/account_scopper"
gem.authors = ["Sebastien Grosjean"]
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
1 change: 1 addition & 0 deletions init.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'account_scopper'
58 changes: 58 additions & 0 deletions lib/account_scopper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module ActiveRecord # :nodoc:
class Base # :nodoc:
class << self # Class methods
alias_method :orig_delete_all, :delete_all
def delete_all(conditions = nil)
if Account.current_account != nil && column_names.include?("account_id")
with_scope(:find => {:conditions => "account_id = "+Account.current_account.id.to_s}) do
orig_delete_all(conditions)
end
else
orig_delete_all(conditions)
end
end

alias_method :orig_calculate, :calculate
def calculate(operation, column_name, options = {})
if Account.current_account != nil && column_names.include?("account_id")
with_scope(:find => {:conditions => ["account_id = ?", Account.current_account.id]}) do
orig_calculate(operation, column_name, options)
end
else
orig_calculate(operation, column_name, options)
end
end

private
alias_method :orig_find_every, :find_every
def find_every(options)
if Account.current_account != nil && column_names.include?("account_id")
with_scope(:find => {:conditions => self.table_name+".`account_id` = "+Account.current_account.id.to_s}) do
orig_find_every(options)
end
else
orig_find_every(options)
end
end
end

# Instance methods, they are called from an instance of a model (an object)
alias_method :orig_destroy, :destroy
def destroy
if !Account.current_account.nil? && respond_to?(:account_id)
orig_destroy if self.account_id == Account.current_account.id
else
orig_destroy
end
end

private
alias_method :orig_create, :create
def create
if !Account.current_account.nil? && respond_to?(:account_id)
self.account_id = Account.current_account.id
end
orig_create
end
end
end
66 changes: 64 additions & 2 deletions spec/account_scopper_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,69 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe "AccountScopper" do
it "fails" do
fail "hey buddy, you should probably rename this file and start specing for real"
# Homemade fixtures method that doesn't depend on active support
fixtures :accounts, :users

it "should proper set fixtures" do
Account.find_by_name("Localhost").should == accounts(:localhost)
end

it "should have the current account initialized" do
Account.current_account.should == accounts(:localhost)
end

it "should create user with account" do
u = User.create
u.account.should == accounts(:localhost)
end

it "should force account while creating new user" do
u = User.create(:account_id => 2)
u.account.should == accounts(:localhost)
end

it "should find user within account" do
User.find(2).should == users(:marc)
end

it "should not find user having different account" do
lambda { User.find(3) }.should raise_error(ActiveRecord::RecordNotFound)
end

it "should return only user from account" do
User.all.each do |user|
user.account.should == accounts(:localhost)
end
end

it "should count only within account" do
User.count.should == User.all.size
end

it "should delete all within account, not others" do
User.delete_all
User.count.should == 0
Account.current_account = nil
User.count.should > 0
end

it "should delete all if no account defined" do
Account.current_account = nil
User.delete_all
User.count.should == 0
end

it "should not allow to destroy foreign users" do
Account.current_account = nil
user = User.find_by_name('Stephane')
user.account.should_not == accounts(:localhost)
Account.current_account = accounts(:localhost)
user.destroy.should be_nil
end

it "should allow to destroy if not account" do
Account.current_account = nil
user = User.find_by_name('Stephane')
user.destroy.should_not be_nil
end
end
21 changes: 21 additions & 0 deletions spec/boot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugin_root = File.join(File.dirname(__FILE__), '..')
version = ENV['RAILS_VERSION']
version = nil if version and version == ""

# first look for a symlink to a copy of the framework
if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
puts "found framework root: #{framework_root}"
# this allows for a plugin to be tested outside of an app and without Rails gems
$:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
else
# simply use installed gems if available
puts "using Rails#{version ? ' ' + version : nil} gems"
require 'rubygems'

if version
gem 'rails', version
else
gem 'actionpack'
gem 'activerecord'
end
end
21 changes: 21 additions & 0 deletions spec/database.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
sqlite2:
:adapter: sqlite
:database: ":memory:"

sqlite3:
:adapter: sqlite3
:database: ":memory:"

postgresql:
:adapter: postgresql
:username: postgres
:password: postgres
:database: account_scopper_test
:min_messages: ERROR

mysql:
:adapter: mysql
:host: localhost
:username: root
:password: password
:database: account_scopper_test
5 changes: 5 additions & 0 deletions spec/fixtures/account.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Account < ActiveRecord::Base
cattr_accessor :current_account

has_many :users
end
6 changes: 6 additions & 0 deletions spec/fixtures/accounts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
localhost:
id: 1
name: Localhost
demo:
id: 2
name: Demo
3 changes: 3 additions & 0 deletions spec/fixtures/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class User < ActiveRecord::Base
belongs_to :account
end
12 changes: 12 additions & 0 deletions spec/fixtures/users.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
seb:
id: 1
account_id: 1
name: Seb
marc:
id: 2
account_id: 1
name: Marc
stephane:
id: 3
account_id: 2
name: Stephane
13 changes: 13 additions & 0 deletions spec/lib/load_fixtures.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'active_record/fixtures'

def load_fixtures
Fixtures.create_fixtures(File.dirname(__FILE__) + "/../fixtures/", ActiveRecord::Base.connection.tables)
end

def fixtures(*args)
args.each do |fixture|
define_method fixture.to_s do |symbol|
eval(fixture.to_s.singularize.camelize).find(Fixtures.all_loaded_fixtures[fixture.to_s][symbol.to_s]["id"])
end
end
end
5 changes: 5 additions & 0 deletions spec/lib/load_models.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def load_models
Dir[File.dirname(__FILE__) + "/../fixtures/*.rb"].entries.compact.each do |str|
load(str)
end
end
32 changes: 32 additions & 0 deletions spec/lib/load_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'active_record'

def load_schema
ActiveRecord::Schema.verbose = false

config = YAML::load(IO.read(File.dirname(__FILE__) + '/../database.yml'))
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/../debug.log")

db_adapter = ENV['DB']

# no db passed, try one of these fine config-free DBs before bombing.
db_adapter ||=
begin
require 'rubygems'
require 'sqlite'
'sqlite'
rescue MissingSourceFile
begin
require 'sqlite3'
'sqlite3'
rescue MissingSourceFile
end
end

if db_adapter.nil?
raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
end

ActiveRecord::Base.establish_connection(config[db_adapter])
load(File.dirname(__FILE__) + "/../schema.rb")
require File.dirname(__FILE__) + '/../../init.rb'
end
9 changes: 9 additions & 0 deletions spec/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ActiveRecord::Schema.define(:version => 0) do
create_table :accounts, :force => true do |t|
t.string :name
end
create_table :users, :force => true do |t|
t.string :name
t.references :account
end
end
19 changes: 17 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'account_scopper'
require 'spec'
require 'spec/autorun'

Spec::Runner.configure do |config|
require 'boot' unless defined?(ActiveRecord)

require File.dirname(__FILE__) + '/lib/load_schema'
require File.dirname(__FILE__) + '/lib/load_models'
require File.dirname(__FILE__) + '/lib/load_fixtures'

Spec::Runner.configure do |config|
load_models

config.before do
load_schema
load_fixtures
Account.current_account = accounts(:localhost)
end

config.after do
Account.current_account = nil
end
end

0 comments on commit b48f161

Please sign in to comment.