acts_as_partitioned 0.0.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff28314db1efa3069fbdb9097112672045cd2b2a
4
+ data.tar.gz: 3a7dcebc0758cc66aeedd53711e502f934c94d15
5
+ SHA512:
6
+ metadata.gz: 59b8231df8d42e6e7f86020afc2da307282c3b918fd7126a2b7c5f495a91d66e2e81c5b5ee9a81b5528d84e8f1887433c15cbde4e25b146b289ecf3029bf8dfb
7
+ data.tar.gz: ed96751cc941bda70c2b766c25f21da0f6cd7fa81dbc1be6263725c9052c3408c0fb0580dd26321856aba92471391ba5da79a0a6d7dc77fa24ccf07c4baced21
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in acts_as_partitioned.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dan Draper
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # ActsAsPartitioned
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'acts_as_partitioned'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install acts_as_partitioned
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/acts_as_partitioned/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acts_as_partitioned/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "acts_as_partitioned"
8
+ spec.version = ActsAsPartitioned::VERSION
9
+ spec.authors = ["Dan Draper"]
10
+ spec.email = ["daniel@codefire.com"]
11
+ spec.summary = %q{A plugin for active record}
12
+ spec.description = %q{Handle postgres style partitions in ActiveRecord}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,36 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Partitioned
4
+ module Cache
5
+ class PartitionCache
6
+ def initialize(keys)
7
+ @pce_fact = PartitionCacheEntryFactory.new(keys)
8
+ @cache = []
9
+ end
10
+
11
+ # TODO: Add cache expiry
12
+ def add(partition)
13
+ @entry = @pce_fact.create
14
+ @entry.partition = partition
15
+ partition.key.each_pair do |key, value|
16
+ @entry.send("#{key}=", value)
17
+ end
18
+ found = self.find(partition.key)
19
+ @cache << @entry unless found
20
+ end
21
+
22
+ def find(hash)
23
+ res = @cache.find do |entry|
24
+ entry == hash
25
+ end
26
+ res ? res.partition : nil
27
+ end
28
+
29
+ def size
30
+ @cache.size
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Partitioned
4
+ module Cache
5
+ class PartitionCacheEntry
6
+ attr_accessor :partition
7
+
8
+ def initialize(keys)
9
+ @keys = keys
10
+ @columns = keys.columns
11
+ end
12
+
13
+ def match(h)
14
+ hash = HashWithIndifferentAccess.new(h)
15
+ @columns.each do |column|
16
+ value = hash[column.to_sym]
17
+ raise "No value provided for #{column} (#{column.class})" unless value
18
+ unless match_instance(column, value)
19
+ return false
20
+ end
21
+ end
22
+ true
23
+ end
24
+ alias :== :match
25
+
26
+ private
27
+ def match_instance(key, value)
28
+ compare_to = instance_variable_get("@#{key}")
29
+ compare_to === value || compare_to == value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Partitioned
4
+ module Cache
5
+ class PartitionCacheEntryFactory
6
+ def initialize(keys)
7
+ @keys = keys
8
+ end
9
+
10
+ def create
11
+ entry = PartitionCacheEntry.new(@keys)
12
+ sing = class << entry; self ; end
13
+ @keys.each do |key|
14
+ sing.send(:define_method, "#{key.column}=") { |arg|
15
+ instance_variable_set("@#{key.column}".to_sym, arg)
16
+ }
17
+ end
18
+ entry
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,108 @@
1
+ require 'active_record/acts/partitioned/cache/partition_cache'
2
+
3
+ module ActiveRecord
4
+ module Acts
5
+ module Partitioned
6
+ class CopyProxy
7
+ def initialize(keys, factory)
8
+ @keys = keys
9
+ @factory = factory
10
+ @cache = Cache::PartitionCache.new(@keys)
11
+ end
12
+
13
+ # determine partition
14
+ # grab the partition keys from the hash (raise if missing)
15
+ # try to find an open copy file
16
+ # A copy file is linked to a partition - if we don't have one then should we fail or build a new part?
17
+ # if not create one
18
+ # expire old copy files
19
+ def <<(hash)
20
+ values = find_key_values(hash)
21
+ partition = @cache.find(values)
22
+ unless partition
23
+ # TODO: If there is no partition for then we need to create one
24
+ # We should provide a creation function - specifically how to create a partition with the desired key range
25
+ partition = @factory.find_for(hash)
26
+ raise "No partition for hash (#{hash.inspect})" unless partition
27
+ @cache.add(partition)
28
+ end
29
+ partition.copy_into << hash
30
+ end
31
+
32
+ private
33
+ def find_key_values(hash)
34
+ values = {}
35
+ hash.each_pair do |key, value|
36
+ if @keys.columns.include?(key)
37
+ values[key] = value
38
+ end
39
+ end
40
+ if values.keys.size < @keys.size
41
+ raise "Not all keys provided to copy data into partition: #{@keys.columns.join(',')} needed"
42
+ end
43
+ values
44
+ end
45
+ end
46
+
47
+
48
+ class CopyFile
49
+ # TODO: Make this a configurable option
50
+ ::COPY_FILE_DIRECTORY = "/tmp/dumps/"
51
+
52
+ def initialize(table_name, options = {})
53
+ @table_name = table_name
54
+ @options = options
55
+ @header_written = false
56
+ @filename = generate_filename
57
+ @file = File.open(::COPY_FILE_DIRECTORY + @filename, "w")
58
+ #write_meta
59
+ end
60
+
61
+ def <<(hash)
62
+ unless @header_written
63
+ write_header(hash.keys)
64
+ end
65
+ # TODO: Write values
66
+ @file << hash.values.map { |v| quote_and_escape(v) }.join(',') << "\n"
67
+ end
68
+
69
+ def close
70
+ @file.close
71
+ end
72
+
73
+ private
74
+ def quote_and_escape(arg)
75
+ # TODO: Escape - see Adam's cortex code
76
+ "\"#{arg}\""
77
+ end
78
+
79
+ def write_meta
80
+ # TODO
81
+ end
82
+
83
+ def write_header(keys)
84
+ # TODO
85
+ @file << "COPY #{@table_name} (#{keys.join(',')}) FROM stdin with csv;\n"
86
+ @header_written = true
87
+ end
88
+
89
+ def generate_filename
90
+ str = "copy_"
91
+ str << @table_name
92
+ if @options.has_key?(:key)
93
+ str << "_#{@options[:key]}"
94
+ end
95
+ str << tmpstr
96
+ end
97
+
98
+ def tmpstr
99
+ str = ""
100
+ 8.times do
101
+ str << ((rand * 25).to_i + 97).chr
102
+ end
103
+ str
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,126 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module Partitioned #:nodoc:
4
+
5
+ # TODO: Rename to Proxy
6
+ # TODO: If we were clever we would merge this with the Partiton AR model - can't merge as you need a proxy instance but we can move lots of methods over
7
+ class Factory
8
+ attr_reader :model, :partition_class
9
+ attr_reader :keys
10
+ delegate :find, :to => :partition_class
11
+ delegate :count, :to => :partition_class
12
+ delegate :with_key, :to => :partition_class
13
+
14
+ def initialize(model, partition_class, options = {})
15
+ @model = model
16
+ @keys = Keys.new
17
+ # TODO: Should we raise if we never add any keys?
18
+ @partition_class = partition_class
19
+ partition_class.factory = self
20
+ # TODO: Raise if model does not have key column(s)
21
+ @options = options
22
+ end
23
+
24
+ def partition_by(column, options = {})
25
+ # TODO: Raise if caller tries to partition on primary key
26
+ @keys << Key.new(column, options)
27
+ end
28
+
29
+ # TODO: Prevent overlapping ranges
30
+ # TODO: Private?
31
+ def set_validations
32
+ # TODO: Move below this line to the partition class itself
33
+ @keys.each do |key|
34
+ case key.type
35
+ when :continuous
36
+ partition_class.validates_uniqueness_of("#{key.column}_begin", :scope => @keys.remaining_columns("#{key.column}_begin"))
37
+ partition_class.validates_uniqueness_of("#{key.column}_end", :scope => @keys.remaining_columns("#{key.column}_end"))
38
+ when :discrete
39
+ partition_class.validates_uniqueness_of(key.column, :scope => @keys.remaining_columns(key.column))
40
+ end
41
+ end
42
+ end
43
+
44
+ def init(options = {:force => false})
45
+ Structure.init_partition_catalog(model, @keys, options)
46
+ end
47
+
48
+ def copy(filename, db_name = nil)
49
+ port, host, user, db = if db_name
50
+ config = ActiveRecord::Base.configurations[db_name.to_s]
51
+ raise "No such DB configuration: #{db_name}" unless config
52
+ [config['port'], config['host'], config['username'], config['database']]
53
+ else
54
+ conn = @model.connection.raw_connection
55
+ [conn.port, conn.host, conn.user, conn.db]
56
+ end
57
+ "psql --set ON_ERROR_STOP=1 --single-transaction -p #{port} -h #{host} -U #{user} #{db} < #{filename}"
58
+ end
59
+
60
+ # Arguments are the keys specified in creation as a hash
61
+ # eg: create(:date => Date.today, :domain => domain)
62
+ def create(key_hash)
63
+ # TODO: Raise if a key missing
64
+ @model.transaction do
65
+ partition = partition_class.create!(key_hash)
66
+ @keys.create_partition_tables(@model, :key_hash => key_hash)
67
+ # TODO: Indexes
68
+ partition
69
+ end
70
+ end
71
+
72
+ def clear
73
+ partition_class.find(:all).each do |partition|
74
+ partition.drop!
75
+ end
76
+ end
77
+
78
+ # Finds a partition to which these keys belong
79
+ # Not by keys used to create the partition
80
+ # This is the same thing for discrete keys
81
+ # but for continuous (ie; ranged keys)
82
+ # the end points of a range may not equal the values
83
+ # stored in the part
84
+ # Here we see if a value fits within the range
85
+ # Use this method if you want to know which partition
86
+ # to write data to
87
+ def find_for(_hash)
88
+ hash = _hash.symbolize_keys
89
+ conditions = {}
90
+ @keys.each do |key|
91
+ puts "key = #{key.inspect}"
92
+ value = hash[key.column.to_sym]
93
+ raise "No value provided for #{key.column}" unless value
94
+ case key.type
95
+ when :discrete
96
+ conditions[key.column.to_sym] = value
97
+ when :continuous
98
+ conditions[:"#{key.column}_begin"] = value.begin
99
+ conditions[:"#{key.column}_end"] = value.end
100
+ conditions[:"#{key.column}_exclusive"] = value.exclude_end?
101
+ end
102
+ end
103
+ puts "conditions = #{conditions.inspect}"
104
+ partition_class.find_by(conditions)
105
+ end
106
+
107
+ def find_or_create_for(hash)
108
+ find_for(hash) || create(hash)
109
+ end
110
+
111
+ def dump_age
112
+ if @options[:dump_age].kind_of?(Proc)
113
+ @options[:dump_age].call || 0
114
+ else
115
+ @options[:dump_age] || 0
116
+ end
117
+ end
118
+
119
+ def archive?
120
+ @options[:archive] || false
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
@@ -0,0 +1,69 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module Partitioned #:nodoc:
4
+ class Key
5
+ attr_accessor :column, :type, :using
6
+
7
+ def initialize(column, options = {})
8
+ @column = column.to_s
9
+ @type = options[:ranged] ? :continuous : :discrete
10
+ end
11
+
12
+ def column_names
13
+ case @type
14
+ when :continuous then ["#{@column}_begin", "#{@column}_end"]
15
+ when :discrete then [@column]
16
+ end
17
+ end
18
+
19
+ def create_partition_table(model, opts = {})
20
+ table = model.table_name.dup
21
+ table << "_part_" + opts[:parent].partition_handle(:key_hash => opts[:key_hash]) if opts.has_key?(:parent)
22
+ partition_name = "#{model.table_name}_part_#{partition_handle(opts)}"
23
+ unless model.connection.tables.include?(partition_name)
24
+ model.connection.execute(<<-SQL)
25
+ CREATE TABLE #{partition_name} (
26
+ CHECK (#{apply_check(opts[:key_hash]).join(' AND ')})
27
+ ) INHERITS (#{table});
28
+ SQL
29
+ end
30
+ end
31
+
32
+ def partition_handle(opts)
33
+ handle = []
34
+ handle << opts[:parent].partition_handle(:key_hash => opts[:key_hash]) if opts.has_key?(:parent)
35
+ value = opts[:key_hash][@column.to_sym]
36
+ handle << case @type
37
+ when :discrete then value
38
+ when :continuous then [ value.begin, value.end ]
39
+ end
40
+ handle.flatten.map { |value|
41
+ handle_partition_value(value)
42
+ }.join("_")
43
+ end
44
+
45
+ private
46
+ def apply_check(key_hash)
47
+ value = key_hash[@column.to_sym]
48
+ unless value
49
+ raise "No value provided for key #{@column}, hash is #{key_hash.inspect}"
50
+ end
51
+ case @type
52
+ when :discrete then ["#{@column} = '#{value}'"]
53
+ when :continuous then ["#{@column} >= '#{value.begin}'", "#{@column} <#{'=' unless value.exclude_end?} '#{value.end}'"]
54
+ end
55
+ end
56
+
57
+ def handle_partition_value(value)
58
+ case value
59
+ when Date,Time,Timestamp
60
+ value.strftime("%Y%m%d%H%M")
61
+ when String
62
+ handle_partition_value(value.to_time) rescue value
63
+ else value
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module Partitioned #:nodoc:
4
+ class Keys < Array
5
+ def columns
6
+ self.map(&:column)
7
+ end
8
+
9
+ def column_names
10
+ self.map { |k| k.column_names }.flatten
11
+ end
12
+
13
+ # Returns the list of column names excluding this one
14
+ def remaining_columns(column)
15
+ self.column_names - [column]
16
+ end
17
+
18
+ def create_partition_tables(model, opts = {})
19
+ each_with_index do |key, index|
20
+ key_opts = index == 0 ? opts : opts.merge(:parent => self[index - 1])
21
+ key.create_partition_table(model, key_opts)
22
+ end
23
+ end
24
+
25
+ def partition_handle(opts)
26
+ map { |k| k.partition_handle(opts) }.join("_")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,121 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module Partitioned #:nodoc:
4
+ class Partition < ActiveRecord::Base
5
+ class_attribute :factory
6
+
7
+ # TODO: WHAT THE HELL??
8
+ def self.with_key(hash)
9
+ self.scoped(:conditions => modified_attrs(hash)).first
10
+ end
11
+
12
+ def self.column_names
13
+ []
14
+ end
15
+
16
+ # Returns true if the hash values should be stored in this partition
17
+ def include?(hash)
18
+ self.key.each_pair do |key, value|
19
+ unless value === hash[key]
20
+ return false
21
+ end
22
+ end
23
+ true
24
+ end
25
+
26
+ def initialize(attrs = {}, options = {})
27
+ super(self.class.modified_attrs(attrs), options)
28
+ end
29
+
30
+ def drop!
31
+ self.transaction do
32
+ if partition_exists?
33
+ self.connection.execute "DROP TABLE #{name}"
34
+ end
35
+ self.destroy
36
+ if num_siblings == 0
37
+ # Delete the parent
38
+ self.connection.execute "DROP TABLE #{parent_name}"
39
+ end
40
+ end
41
+ end
42
+
43
+ def num_siblings
44
+ parent = self.class.factory.keys[-2] # second to last
45
+ if parent
46
+ # TODO: This won't handle a ranged parent yet
47
+ self.class.count(:conditions => { parent.column => attributes[parent.column] })
48
+ end
49
+ end
50
+
51
+ # Will unlink the partition from the parent table but not delete
52
+ def unlink
53
+ self.transaction do
54
+ self.class.factory.model.connection.execute <<-SQL
55
+ ALTER TABLE #{name} NO INHERIT #{self.class.factory.model.table_name};
56
+ ALTER TABLE #{name} RENAME TO #{name}_unlinked;
57
+ SQL
58
+ self.destroy
59
+ end
60
+ end
61
+
62
+ def dump
63
+ conn = self.class.factory.model.connection.raw_connection
64
+ raise "Partition does not exist" unless partition_exists?
65
+ `pg_dump -h #{conn.host} -U #{conn.user} -t #{self.tablename} #{conn.db} | gzip`
66
+ end
67
+
68
+ def size
69
+ rec = self.class.find(:first, :select => "relpages", :from => "pg_class", :conditions => "relname = '#{self.tablename}'")
70
+ rec[:relpages].to_i * 8192
71
+ end
72
+
73
+ def name
74
+ "#{self.class.factory.model.table_name}_part_#{self.class.factory.keys.partition_handle(:key_hash => key)}"
75
+ end
76
+
77
+ def parent_name
78
+ return nil unless self.class.factory.keys[-2]
79
+ "#{self.class.factory.model.table_name}_part_#{self.class.factory.keys[-2].partition_handle(:key_hash => key)}"
80
+ end
81
+
82
+ def partition_exists?
83
+ self.connection.table_exists?(name)
84
+ end
85
+
86
+ # Modify parameters to suit ranges if required
87
+ private
88
+ def self.modified_attrs(attrs)
89
+ hash = {}
90
+ attrs.each_pair do |key, value|
91
+ if value.instance_of?(Range)
92
+ hash["#{key}_begin"] = value.begin
93
+ hash["#{key}_end"] = value.end
94
+ hash["#{key}_exclusive"] = value.exclude_end?
95
+ else
96
+ hash[key] = value
97
+ end
98
+ end
99
+ hash
100
+ end
101
+
102
+ def key
103
+ hash = HashWithIndifferentAccess.new
104
+ self.class.factory.keys.each do |k|
105
+ case k.type
106
+ when :continuous
107
+ r_start = self.send("#{k.column}_begin")
108
+ r_end = self.send("#{k.column}_end")
109
+ r_exclusive = self.send("#{k.column}_exclusive")
110
+ hash[k.column] = Range.new(r_start, r_end, r_exclusive)
111
+ when :discrete
112
+ hash[k.column] = self.send(k.column)
113
+ end
114
+ end
115
+ hash
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,30 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Partitioned
4
+ class Structure < ActiveRecord::Migration
5
+ def self.init_partition_catalog(model, keys, options = {})
6
+ create_table("#{model.table_name}_partitions", :force => options[:force]) do |t|
7
+ puts "keys are '#{keys.inspect}'"
8
+ keys.each do |key|
9
+ case key.type
10
+ when :discrete
11
+ t.column key.column, determine_column_type(model, key.column)
12
+ when :continuous
13
+ t.column "#{key.column}_begin", determine_column_type(model, key.column)
14
+ t.column "#{key.column}_end", determine_column_type(model, key.column)
15
+ t.column "#{key.column}_exclusive", :boolean
16
+ end
17
+ end
18
+ # TODO: Add key columns and indexes
19
+ end
20
+ end
21
+
22
+ def self.determine_column_type(model, column)
23
+ found = model.columns.detect { |c| c.name == column.to_s }
24
+ raise "No such column '#{column}' on model '#{model}'" unless found
25
+ found.type
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ require 'active_record/acts/partitioned/key'
2
+ require 'active_record/acts/partitioned/keys'
3
+ require 'active_record/acts/partitioned/partition'
4
+ require 'active_record/acts/partitioned/factory'
5
+ require 'active_record/acts/partitioned/structure'
6
+ require 'active_record/acts/partitioned/copy_proxy'
7
+
8
+ # ActsAsPartitioned
9
+ module ActiveRecord
10
+ class Base
11
+ class << self
12
+ def partitioned_classes
13
+ @@subclasses[ActiveRecord::Base].select(&:partitioned?)
14
+ end
15
+ end
16
+ end
17
+
18
+ module Acts #:nodoc:
19
+ module Partitioned #:nodoc:
20
+ class PartitionError < StandardError ; end
21
+
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ base.class_attribute :partitions
25
+ end
26
+
27
+ module ClassMethods
28
+ def partition(*args)
29
+ options = args.extract_options!
30
+ # TODO: Use an anonymous class
31
+ eval <<-EVAL
32
+ class ActiveRecord::Acts::Partitioned::#{self.name}Partition < ActiveRecord::Acts::Partitioned::Partition
33
+ self.table_name = '#{self.table_name}_partitions'
34
+ end
35
+ EVAL
36
+ klass = "ActiveRecord::Acts::Partitioned::#{self.name}Partition".constantize
37
+ factory = Factory.new(self, klass, options)
38
+ args.each { |arg| factory.partition_by(key) }
39
+ yield factory if block_given?
40
+ factory.set_validations
41
+ self.partitions = factory
42
+
43
+ # TODO: Put this in sep rake task and call on factory - should this be called Proxy
44
+ #factory.migrate(:force => true)
45
+ end
46
+
47
+ def partitions
48
+ nil
49
+ end
50
+
51
+ def partitioned?
52
+ self.partitions.present?
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsPartitioned
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "acts_as_partitioned/version"
2
+ require 'active_record/acts/partitioned'
3
+
4
+ module ActsAsPartitioned
5
+ # Your code goes here...
6
+ end
7
+
8
+ ActiveRecord::Base.send(:include, ActiveRecord::Acts::Partitioned)
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_partitioned
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Dan Draper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Handle postgres style partitions in ActiveRecord
42
+ email:
43
+ - daniel@codefire.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - acts_as_partitioned.gemspec
54
+ - lib/active_record/acts/partitioned.rb
55
+ - lib/active_record/acts/partitioned/.key.rb.swp
56
+ - lib/active_record/acts/partitioned/cache/partition_cache.rb
57
+ - lib/active_record/acts/partitioned/cache/partition_cache_entry.rb
58
+ - lib/active_record/acts/partitioned/cache/partition_cache_entry_factory.rb
59
+ - lib/active_record/acts/partitioned/copy_proxy.rb
60
+ - lib/active_record/acts/partitioned/factory.rb
61
+ - lib/active_record/acts/partitioned/key.rb
62
+ - lib/active_record/acts/partitioned/keys.rb
63
+ - lib/active_record/acts/partitioned/partition.rb
64
+ - lib/active_record/acts/partitioned/structure.rb
65
+ - lib/acts_as_partitioned.rb
66
+ - lib/acts_as_partitioned/version.rb
67
+ homepage: ''
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.2.2
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: A plugin for active record
91
+ test_files: []