rails-sharding 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/fixtures/shards.yml
10
+ /spec/fixtures/schemas
11
+ /tmp/
12
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ rails-sharding
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.3.1
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ services:
6
+ - mysql
7
+ before_install: gem install bundler -v 1.12.4
8
+ before_script:
9
+ - cp spec/fixtures/shards.yml.travis spec/fixtures/shards.yml
10
+ - rake db:test:prepare
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rails-sharding.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Henrique Gubert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # Rails::Sharding
2
+
3
+ [![Build Status](https://travis-ci.org/hsgubert/rails-sharding.svg?branch=master)](https://travis-ci.org/hsgubert/rails-sharding)
4
+ [![Code Climate](https://codeclimate.com/github/hsgubert/rails-sharding/badges/gpa.svg)](https://codeclimate.com/github/hsgubert/rails-sharding)
5
+
6
+
7
+ Simple and robust sharding for Rails, including Migrations and ActiveRecord extensions
8
+
9
+ This gems allows you to easily create extra databases to your rails application, and freely allocate ActiveRecord instances to any of the databases. It also provides rake tasks and migrations to help you manage the schema by shard groups.
10
+
11
+ After you have setup your shards, accessing them is as simple as:
12
+ ```ruby
13
+ new_user = User.using_shard(:shard_group1, :shard1).create(username: 'x')
14
+ loaded_user = User.using_shard(:shard_group1, :shard1).where(username: 'x').first
15
+ ```
16
+
17
+ You can also use the block syntax, where all your queries inside will be directed to the correct shard:
18
+ ```ruby
19
+ Rails::Sharding.using_shard(:shard_group1, :shard1) do
20
+ new_user = User.create(username: 'x')
21
+ loaded_user = User.where(username: 'x').first
22
+ billing_infos = loaded_user.billing_infos.all
23
+ end
24
+ ```
25
+
26
+ You can also pick and choose which models will be shardable, so that all the models that are not shardable will still be retrieved from the master database, even if inside a using_shard block.
27
+
28
+ ## Compatibility
29
+ As of now this gem has been tested only with Rails 4.2. It does not work yet with Rails 5.
30
+
31
+ ## Installation
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ ```ruby
36
+ gem 'rails-sharding'
37
+ ```
38
+
39
+ And then execute:
40
+ ```
41
+ bundle
42
+ ```
43
+
44
+ ## Creating Shards
45
+ This gem helps you create shards that are additional and completely separate from your master database. The master database is the one that is created and managed through rails, and is the default storage for all your models.
46
+
47
+ To start with the rails-sharding gem, run the command
48
+ ```
49
+ rails g rails_sharding:scaffold
50
+ ```
51
+
52
+ This will generate a `config/shards.yml.example` like this:
53
+ ```ruby
54
+ default: &default
55
+ adapter: mysql2
56
+ encoding: utf8
57
+ reconnect: false
58
+ pool: 5
59
+ username: ___
60
+ password: ___
61
+ socket: /var/run/mysqld/mysqld.sock
62
+
63
+ development:
64
+ shard_group1:
65
+ shard1:
66
+ <<: *default
67
+ database: group1_shard1_development
68
+ shard2:
69
+ <<: *default
70
+ database: group1_shard2_development
71
+ ...
72
+ ```
73
+
74
+ Rename it to `config/shards.yml` and change it to your database configuration. This example file defines a single shard group (named `shard_group1`) containing two shards (`shard1` and `shard2`). A shard group is simply a set of shards that should have the same schema.
75
+
76
+ When you're ready to create the shards run
77
+ ```
78
+ rake shards:create
79
+ ```
80
+
81
+ ## Migrating Shards
82
+ Go to the directory `db/shards_migrations/shard_group1` and add all migrations that you want to run on the shards of `shard_group1`. By design, all shards in a same group should always have the same schema. For example, add the following migration to your `db/shards_migrations/shard_group1`:
83
+ ```ruby
84
+ # 20160808000000_create_users.rb
85
+ class CreateClients < ActiveRecord::Migration
86
+ def up
87
+ create_table :users do |t|
88
+ t.string :username, :limit => 100
89
+ t.timestamps
90
+ end
91
+ end
92
+
93
+ def down
94
+ drop_table :users
95
+ end
96
+ end
97
+ ```
98
+
99
+ Then run:
100
+ ```
101
+ rake shards:migrate
102
+ ```
103
+
104
+ All the shards will be migrated, and one schema file will be dumped for each of the shards (just like rails would do for your master database). You can see the schema of the shards in `db/shards_schemas/shard_group1/`, and it will be something like:
105
+ ```ruby
106
+ ActiveRecord::Schema.define(version: 20160808000000) do
107
+
108
+ create_table "users", force: :cascade do |t|
109
+ t.string "username", limit: 100
110
+ t.datetime "created_at"
111
+ t.datetime "updated_at"
112
+ end
113
+
114
+ end
115
+ ```
116
+
117
+ ## Other rake tasks
118
+ The rails-sharding gem offers several rake tasks analogous to the ones offered by ActiveRecord:
119
+ ```
120
+ rake shards:create
121
+ rake shards:drop
122
+ rake shards:migrate
123
+ rake shards:migrate:down
124
+ rake shards:migrate:redo
125
+ rake shards:migrate:reset
126
+ rake shards:migrate:up
127
+ rake shards:rollback
128
+ rake shards:schema:dump
129
+ rake shards:schema:load
130
+ rake shards:test:load_schema
131
+ rake shards:test:prepare
132
+ rake shards:test:purge
133
+ rake shards:version
134
+ ```
135
+
136
+ They work just the same as the tasks `rake:db:...` but they operate on all shards of all shard groups. If you want to run a rake task just to a specific shard group or shard you can use the `SHARD_GROUP` and `SHARD` options:
137
+ ```
138
+ rake shards:migrate SHARD_GROUP=shard_group_1
139
+ rake shards:migrate SHARD_GROUP=shard_group_1 SHARD=shard1
140
+ ```
141
+
142
+
143
+ ## Development and Contributing
144
+
145
+ After checking out the repo, run `bundle` to install gems and run `rake db:test:prepare` to create the test shards. Then, run `rspec` to run the tests.
146
+
147
+ Bug reports and pull requests are welcome on GitHub at https://github.com/hsgubert/rails-sharding.
148
+
149
+
150
+ ## License
151
+
152
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
153
+
154
+ ## Acknowledgements
155
+
156
+ This gem was inspired and based on several other gems like: [octopus](https://github.com/thiagopradi/octopus), [shard_handler](https://github.com/locaweb/shard_handler) and [active_record_shards](https://github.com/zendesk/active_record_shards).
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ # defines an environment task so we can run rake tasks from lib/tasks/rails-sharding.rake.
9
+ # They require the :environment task, which is defined by rails, but we don need anything
10
+ task :environment do
11
+ # do nothing
12
+ end
13
+
14
+ namespace :db do
15
+ namespace :test do
16
+
17
+ desc 'Loads gem test environment and rake tasks from gem'
18
+ task :load_env do
19
+ require 'rspec'
20
+
21
+ # requires spec helper but ensures no test coverage is reported to codeclimate
22
+ ENV['CODECLIMATE_REPO_TOKEN'] = nil
23
+ require './spec/spec_helper'
24
+
25
+ load 'lib/tasks/rails-sharding.rake'
26
+ end
27
+
28
+ desc "Creates database shards for testing the gem"
29
+ task create: [:load_env] do
30
+ # simply calls shards:create as it will work properly after spec_helper
31
+ # changed the shards configuration
32
+ Rake::Task['shards:create'].invoke
33
+ end
34
+
35
+ desc "Drops database shards for testing the gem"
36
+ task drop: [:load_env] do
37
+ Rake::Task['shards:drop'].invoke
38
+ end
39
+
40
+ desc "Migrates database shards for testing the gem"
41
+ task migrate: [:load_env] do
42
+ Rake::Task['shards:migrate'].invoke
43
+ end
44
+
45
+ desc "Prepares database shards for testing the gem (this will clear and recreate database)"
46
+ task prepare: [:load_env] do
47
+ Rake::Task['db:test:drop'].invoke
48
+ Rake::Task['db:test:create'].invoke
49
+ Rake::Task['db:test:migrate'].invoke
50
+ end
51
+ end
52
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rails/sharding"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ require 'rails/generators'
2
+
3
+ module RailsSharding
4
+ class ScaffoldGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def copy_initializer
8
+ copy_file 'rails-sharding_initializer.rb', 'config/initializers/rails-sharding.rb'
9
+ end
10
+
11
+ def copy_configuration_file_and_example
12
+ copy_file 'shards.yml.example', Rails::Sharding::Config.shards_config_file + '.example'
13
+ copy_file 'shards.yml.example', Rails::Sharding::Config.shards_config_file
14
+ end
15
+
16
+ def add_configuration_to_gitignore
17
+ append_to_file '.gitignore' do
18
+ "\n" + Rails::Sharding::Config.shards_config_file
19
+ end
20
+ end
21
+
22
+ def create_migrations_and_schema_directory
23
+ empty_directory Rails::Sharding::Config.shards_migrations_dir
24
+ empty_directory Rails::Sharding::Config.shards_schemas_dir
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+
2
+ Rails::Sharding.setup do |config|
3
+
4
+ end
@@ -0,0 +1,35 @@
1
+ default: &default
2
+ adapter: mysql2
3
+ encoding: utf8
4
+ reconnect: false
5
+ pool: 5
6
+ username: ___
7
+ password: ___
8
+ socket: /var/run/mysqld/mysqld.sock
9
+
10
+ development:
11
+ shard_group1:
12
+ shard1:
13
+ <<: *default
14
+ database: group1_shard1_development
15
+ shard2:
16
+ <<: *default
17
+ database: group1_shard2_development
18
+
19
+ test:
20
+ shard_group1:
21
+ shard1:
22
+ <<: *default
23
+ database: group1_shard1_test
24
+ shard2:
25
+ <<: *default
26
+ database: group1_shard2_test
27
+
28
+ production:
29
+ shard_group1:
30
+ shard1:
31
+ <<: *default
32
+ database: group1_shard1
33
+ shard2:
34
+ <<: *default
35
+ database: group1_shard2
@@ -0,0 +1,18 @@
1
+ require 'rails'
2
+
3
+ require 'rails/sharding/version'
4
+ require 'rails/sharding/core'
5
+ require 'generators/scaffold_generator'
6
+
7
+ require 'rails/sharding/railtie' if defined?(Rails::Railtie)
8
+
9
+ module Rails
10
+ module Sharding
11
+
12
+ # delegates all methods to Core, to shorten method calls
13
+ def self.method_missing(method_sym, *arguments, &block)
14
+ Core.send(method_sym, *arguments, &block)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,90 @@
1
+
2
+ module Rails::Sharding
3
+ module ActiveRecordExtensions
4
+ # Will automatically add the #using_shard method to all ActiveRecord scopes
5
+ # (including has_many and habtm relations).
6
+ #
7
+ # Despite the fact that the #using_shard method will exist for all scopes, it
8
+ # will only have effect when loading/saving models that include the
9
+ # Rails::Sharding::Shardable module
10
+ def self.extend_active_record_scope
11
+ # avoinds duplicate extension
12
+ return if ActiveRecord::Base.respond_to? :using_shard
13
+
14
+ # Includes #using_shard in ActiveRecord::Base models (both classes and instances)
15
+ ActiveRecord::Base.extend ScopeMethods
16
+ ActiveRecord::Base.include ScopeMethods
17
+ ActiveRecord::Base.extend CaseFixer
18
+
19
+ # Includes #using_shard scope method in scopes
20
+ ActiveRecord::Relation.include ScopeMethods
21
+ ActiveRecord::Relation.extend CaseFixer
22
+
23
+ # Includes #using_shard scope method in has_many and habtm relations
24
+ ActiveRecord::Scoping.include ScopeMethods
25
+ ActiveRecord::Scoping.extend CaseFixer
26
+ end
27
+
28
+ module ScopeMethods
29
+ def using_shard(shard_group, shard_name)
30
+ if block_given?
31
+ raise Errors::WrongUsageError,
32
+ "#{name}.using is not allowed to receive a block, it works just like a regular scope.\nIf you are trying to scope everything to a specific shard, use Shards::Core.using_shard instead."
33
+ end
34
+
35
+ ScopeProxy.new(shard_group, shard_name, self)
36
+ end
37
+ end
38
+
39
+ # Return value of the #using_shard scope method. Allows us to chain the shard
40
+ # choice with other ActiveRecord scopes
41
+ class ScopeProxy
42
+ attr_accessor :original_scope
43
+
44
+ def initialize(shard_group, shard_name, original_scope)
45
+ @shard_group = shard_group
46
+ @shard_name = shard_name
47
+ @original_scope = original_scope
48
+ end
49
+
50
+ # if using_shard is called twice in a chain, just replaces configuration
51
+ def using_shard(shard_group, shard_name)
52
+ @shard_group = shard_group
53
+ @shard_name = shard_name
54
+ self
55
+ end
56
+
57
+ def method_missing(method, *args, &block)
58
+ # runs any method chained in the correct shard
59
+ result = Core.using_shard(@shard_group, @shard_name) do
60
+ @original_scope.send(method, *args, &block)
61
+ end
62
+
63
+ # if result is still a scope (responds to to_sql), update original scope
64
+ # and return proxy to continue chaining
65
+ if result.respond_to?(:to_sql)
66
+ @original_scope = result
67
+ return self
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ # Delegates == to method_missing so that User.using_scope(:a,:b).where(:name => "Mike")
74
+ # gets run in the correct shard context when #== is evaluated.
75
+ def ==(other)
76
+ method_missing(:==, other)
77
+ end
78
+ alias_method :eql?, :==
79
+ end
80
+
81
+ # Fixes case when behavior when ScopeProxy is passed to case
82
+ # (otherwise classes don't match)
83
+ module CaseFixer
84
+ def ===(other)
85
+ other = other.original_scope while other === ScopeProxy
86
+ super
87
+ end
88
+ end
89
+ end
90
+ end