ar-ondemand 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module ArOnDemand
2
+ VERSION = '1.0.1'
3
+ end
@@ -0,0 +1,2 @@
1
+ require_dependency 'ar-ondemand/on_demand'
2
+ require_dependency 'ar-ondemand/for_reading'
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: