ar-ondemand 1.0.1
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 +15 -0
- data/README.md +1 -0
- data/ar-ondemand.gemspec +25 -0
- data/lib/ar-ondemand/for_reading.rb +63 -0
- data/lib/ar-ondemand/on_demand.rb +58 -0
- data/lib/ar-ondemand/record.rb +69 -0
- data/lib/ar-ondemand/result.rb +54 -0
- data/lib/ar-ondemand/version.rb +3 -0
- data/lib/ar-ondemand.rb +2 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Yjk2ZDkxMGM4MDFkY2E4MmVhY2U0ZjVhNDFiZjE5YmMyZmY2ZjkyYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MGYwY2ZlNzZkYjZhMjYxNGVkYTUyZjljMTQwZDVkMDQyYTVhNWNhNg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NDA3ZmVlZDQxNzgyZDUwYjE2MmQwZWNiNjJkYmIyNGJhMjQyMmY0ZGRmMDY5
|
10
|
+
MWQ0ZWNmZWVjNTFjNzk0N2M0NjRlNzg0NTFjMzgxMTAyNjUwMzliMzQzZjJl
|
11
|
+
ZTRkZjYzNDVjOWI5N2YzZmQ3ZTU4NjRjMWY2NWJmMjIxZjA5MTE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MzA3Yjc4YmQ3ODA4NTIxMTFjNzIyY2M4MjhkMGM0MTQ4YTM3YTJlMTgxNWQy
|
14
|
+
ZWNhZDFiNjQ0OTA3YzczODE5ZDcwMGRkODhhODU5NDVhMDU3ZGMyODkwODll
|
15
|
+
OTkxMTgwMzFmYjY4MTJlYmYzZGJiYWFkOTZmN2Y2NTZkODMwZTM=
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# ar-ondemand
|
data/ar-ondemand.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require File.dirname(__FILE__) + '/lib/ar-ondemand/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ar-ondemand'
|
7
|
+
s.version = ::ArOnDemand::VERSION
|
8
|
+
s.date = '2015-01-06'
|
9
|
+
s.summary = 'ActiveRecord On-demand'
|
10
|
+
s.description = 'Fast access to database results without the memory overhead of ActiveRecord objects'
|
11
|
+
s.authors = ['Steve Frank']
|
12
|
+
s.email = %w(steve@cloudhealthtech.com lardcanoe@gmail.com)
|
13
|
+
s.homepage = 'https://github.com/CloudHealth/ar-ondemand'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
|
21
|
+
s.require_paths = %w(lib)
|
22
|
+
|
23
|
+
s.add_dependency 'activesupport', '>= 3.2'
|
24
|
+
s.add_dependency 'activerecord', '>= 3.2'
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'ar-ondemand/result'
|
3
|
+
require 'ar-ondemand/record'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module OnDemand
|
7
|
+
module ForReadingExtension
|
8
|
+
extend ::ActiveSupport::Concern
|
9
|
+
|
10
|
+
# Ripped from the find_in_batches function, but customized to return an ::ActiveRecord::OnDemand::ResultSet
|
11
|
+
def for_reading(options = {})
|
12
|
+
return query(self) if options.empty?
|
13
|
+
|
14
|
+
relation = self
|
15
|
+
|
16
|
+
unless arel.orders.blank? && arel.taken.blank?
|
17
|
+
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
18
|
+
end
|
19
|
+
|
20
|
+
if (finder_options = options.except(:start, :batch_size)).present?
|
21
|
+
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
|
22
|
+
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
|
23
|
+
|
24
|
+
relation = apply_finder_options(finder_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
start = options.delete(:start)
|
28
|
+
batch_size = options.delete(:batch_size) || 1000
|
29
|
+
|
30
|
+
relation = relation.reorder(batch_order).limit(batch_size)
|
31
|
+
records = start ? query(relation.where(table[primary_key].gteq(start))) : query(relation)
|
32
|
+
|
33
|
+
while records.any?
|
34
|
+
records_size = records.size
|
35
|
+
primary_key_offset = records.last.id
|
36
|
+
|
37
|
+
yield records
|
38
|
+
|
39
|
+
break if records_size < batch_size
|
40
|
+
|
41
|
+
if primary_key_offset
|
42
|
+
records = query relation.where(table[primary_key].gt(primary_key_offset))
|
43
|
+
else
|
44
|
+
raise "Primary key not included in the custom select clause"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def query(ar)
|
52
|
+
results = ::ActiveRecord::Base.connection.exec_query ar.to_sql
|
53
|
+
::ActiveRecord::OnDemand::ResultSet.new self.arel.engine, results
|
54
|
+
end
|
55
|
+
|
56
|
+
def batch_order
|
57
|
+
"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
::ActiveRecord::Relation.send(:include, ::ActiveRecord::OnDemand::ForReadingExtension)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'ar-ondemand/result'
|
3
|
+
require 'ar-ondemand/record'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module OnDemand
|
7
|
+
class Result < ResultSet
|
8
|
+
include ::Enumerable
|
9
|
+
|
10
|
+
def initialize(model, results, key_column, defaults)
|
11
|
+
super(model, results)
|
12
|
+
|
13
|
+
raise "Key column cannot be blank." if key_column.blank?
|
14
|
+
raise "Defaults cannot be empty." if defaults.empty?
|
15
|
+
|
16
|
+
@key_column = key_column.to_s
|
17
|
+
@defaults = defaults
|
18
|
+
|
19
|
+
@key_index = @col_indexes[@key_column]
|
20
|
+
raise "Unknown index #{key_column}" if @key_index.nil?
|
21
|
+
|
22
|
+
@find_by_method = "find_or_initialize_by_#{(@defaults.keys + [@key_column]).join('_and_')}"
|
23
|
+
@has_any_assets = !ids.empty? || @model.unscoped.where(@defaults).exists?
|
24
|
+
@new_params = @defaults.dup
|
25
|
+
@new_params[@key_column.to_sym] = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](key)
|
29
|
+
raise "Search key cannot be blank." if key.blank?
|
30
|
+
rec = @results.rows.find { |x| x[@key_index] == key }
|
31
|
+
if rec.nil?
|
32
|
+
if @has_any_assets
|
33
|
+
args = @defaults.values + [key]
|
34
|
+
@model.unscoped.send(@find_by_method, *args)
|
35
|
+
else
|
36
|
+
@new_params[@key_column.to_sym] = key
|
37
|
+
@model.new @new_params
|
38
|
+
end
|
39
|
+
else
|
40
|
+
::ActiveRecord::OnDemand::Record.new convert_to_hash(rec), @model, @defaults
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Extension
|
46
|
+
extend ::ActiveSupport::Concern
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
def on_demand(keys, defaults = {})
|
50
|
+
results = ::ActiveRecord::Base.connection.exec_query self.where(defaults).to_sql
|
51
|
+
::ActiveRecord::OnDemand::Result.new self, results, keys, defaults
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
::ActiveRecord::Base.send(:include, ::ActiveRecord::OnDemand::Extension)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module OnDemand
|
3
|
+
class Record
|
4
|
+
#TODO: include ::ActiveModel::Dirty
|
5
|
+
attr_reader :changes
|
6
|
+
|
7
|
+
def initialize(record, model, defaults)
|
8
|
+
@record = record
|
9
|
+
@model = model
|
10
|
+
@defaults = defaults
|
11
|
+
@changes = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def is_readonly?
|
15
|
+
@defaults.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(meth, *args, &block)
|
19
|
+
key = meth.to_s
|
20
|
+
if @record.include? key
|
21
|
+
@changes.include?(key) ? @changes[key] : @record[key]
|
22
|
+
elsif key.end_with?('=') && @record.include?(key[0...-1])
|
23
|
+
raise ActiveRecord::ReadOnlyRecord if is_readonly?
|
24
|
+
key = key[0...-1]
|
25
|
+
unless key == 'id'
|
26
|
+
if @record[key] != args[0]
|
27
|
+
return if @record[key] == 1 && args[0] == true
|
28
|
+
return if @record[key] == 0 && args[0] == false
|
29
|
+
return if args[0].is_a?(Time) && !@record[key].blank? && @record[key].to_time.to_i == args[0].to_i
|
30
|
+
@changes[key] = args[0]
|
31
|
+
else
|
32
|
+
# If they changed it, then reverted back, remove from @changes
|
33
|
+
@changes.delete key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
elsif key.end_with?('?') && @record.include?(key[0...-1])
|
37
|
+
key = key[0...-1]
|
38
|
+
v = @changes.include?(key) ? @changes[key] : @record[key]
|
39
|
+
return false if v.nil?
|
40
|
+
return false if v === 0 || v === false
|
41
|
+
return false if v.respond_to?(:blank?) && v.blank?
|
42
|
+
true
|
43
|
+
elsif key.end_with?('_changed?') && @record.include?(key[0...-9])
|
44
|
+
@changes.include?(key[0...-9])
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_attribute?(attr)
|
51
|
+
@record.include? attr
|
52
|
+
end
|
53
|
+
|
54
|
+
def save
|
55
|
+
raise ActiveRecord::ReadOnlyRecord if is_readonly?
|
56
|
+
return nil if @changes.empty?
|
57
|
+
# logger.debug "Loading model to store changes: #{@changes}"
|
58
|
+
rec = @model.allocate.init_with('attributes' => @record)
|
59
|
+
@changes.each_pair do |key, val|
|
60
|
+
next if key == 'id'
|
61
|
+
rec[key] = val
|
62
|
+
end
|
63
|
+
rec.save
|
64
|
+
@changes.clear
|
65
|
+
rec
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module OnDemand
|
3
|
+
class ResultSet
|
4
|
+
include ::Enumerable
|
5
|
+
|
6
|
+
def initialize(model, results)
|
7
|
+
@model = model
|
8
|
+
@results = results
|
9
|
+
@column_types = Hash[@model.columns.map { |x| [x.name, x] }]
|
10
|
+
@col_indexes = HashWithIndifferentAccess[@results.columns.each_with_index.map { |x, i| [x,i] }]
|
11
|
+
end
|
12
|
+
|
13
|
+
def ids
|
14
|
+
id_col = @col_indexes[:id]
|
15
|
+
@results.rows.map { |r| r[id_col] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def length
|
19
|
+
@results.rows.length
|
20
|
+
end
|
21
|
+
alias_method :count, :length
|
22
|
+
alias_method :size, :length
|
23
|
+
|
24
|
+
def each
|
25
|
+
@results.rows.each do |row|
|
26
|
+
yield ::ActiveRecord::OnDemand::Record.new(convert_to_hash(row), @model, nil)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def first
|
31
|
+
row = @results.rows.first
|
32
|
+
return nil if row.nil?
|
33
|
+
::ActiveRecord::OnDemand::Record.new convert_to_hash(row), @model, nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def last
|
37
|
+
row = @results.rows.last
|
38
|
+
return nil if row.nil?
|
39
|
+
::ActiveRecord::OnDemand::Record.new convert_to_hash(row), @model, nil
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def convert_to_hash(rec)
|
45
|
+
# TODO: Is using HashWithIndifferentAccess[] more efficient?
|
46
|
+
h = {}
|
47
|
+
@col_indexes.each_pair do |k, v|
|
48
|
+
h[k] = @column_types[k].type_cast rec[v]
|
49
|
+
end
|
50
|
+
h.with_indifferent_access
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/ar-ondemand.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ar-ondemand
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Frank
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
description: Fast access to database results without the memory overhead of ActiveRecord
|
42
|
+
objects
|
43
|
+
email:
|
44
|
+
- steve@cloudhealthtech.com
|
45
|
+
- lardcanoe@gmail.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- README.md
|
51
|
+
- ar-ondemand.gemspec
|
52
|
+
- lib/ar-ondemand.rb
|
53
|
+
- lib/ar-ondemand/for_reading.rb
|
54
|
+
- lib/ar-ondemand/on_demand.rb
|
55
|
+
- lib/ar-ondemand/record.rb
|
56
|
+
- lib/ar-ondemand/result.rb
|
57
|
+
- lib/ar-ondemand/version.rb
|
58
|
+
homepage: https://github.com/CloudHealth/ar-ondemand
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.3.0
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: ActiveRecord On-demand
|
82
|
+
test_files: []
|
83
|
+
has_rdoc:
|