light_record 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: 68b18b821e94f95075db330967d3cb14537ef4b3
4
+ data.tar.gz: 6463993d97d10f007faee6c14fda80c2efad8c1e
5
+ SHA512:
6
+ metadata.gz: fc8ebbe93aa163cfc93d1368f3cbfa6c90fa0893d9476336f908aea7f135c76e105515555b19226f2df6c856c3be86047625ad6a0039ebd4baa107d077fccbd4
7
+ data.tar.gz: 4735d36300f2747a7233e728d05dcd143cd6103b361ded30293351c6754015744c7894609b68147c54d00b94b669fbe710501bcc9da6ceb1aeac948f2e41f78b
@@ -0,0 +1,193 @@
1
+ module LightRecord
2
+ extend self
3
+
4
+ # Create LightRecord class based on klass argument
5
+ # Used internally by scope#light_records and scope#light_records_each
6
+ #
7
+ # @param klass [Class] ActiveRecord model
8
+ # @param fields [Array] list of fields, will be used to define attribute methods
9
+ # @return new class base on klass argument but extended with LightRecord methods
10
+ #
11
+ def build_for_class(klass, fields)
12
+ new_klass = Class.new(klass) do
13
+ self.table_name = klass.table_name
14
+ self.inheritance_column = nil
15
+
16
+ extend LightRecord::RecordAttributes
17
+
18
+ define_fields(fields)
19
+
20
+ def initialize(data)
21
+ @data = data
22
+ @attributes = @data
23
+ @readonly = true
24
+ end
25
+
26
+ def read_attribute_before_type_cast(attr_name)
27
+ @data[attr_name.to_sym]
28
+ end
29
+
30
+ def self.subclass_from_attributes?(attrs)
31
+ false
32
+ end
33
+
34
+ def has_attribute?(attr_name)
35
+ @attributes.has_key?(attr_name.to_sym)
36
+ end
37
+
38
+ def read_attribute(attr_name)
39
+ @attributes[attr_name.to_sym]
40
+ end
41
+
42
+ def _read_attribute(attr_name)
43
+ @attributes.fetch(attr_name.to_sym) { |n| yield n if block_given? }
44
+ end
45
+
46
+ def [](attr_name)
47
+ @attributes[attr_name.to_sym]
48
+ end
49
+
50
+ def attributes
51
+ @attributes
52
+ end
53
+
54
+ # to avoid errors when try saving data
55
+ def remember_transaction_record_state
56
+ @_start_transaction_state ||= {}
57
+ super
58
+ end
59
+ end
60
+
61
+ if klass.const_defined?(:LightRecord, false)
62
+ new_klass.send(:include, klass::LightRecord)
63
+ elsif klass.superclass.const_defined?(:LightRecord, false)
64
+ new_klass.send(:include, klass.superclass::LightRecord)
65
+ end
66
+
67
+ new_klass
68
+ end
69
+
70
+ # ActiveRecord extension for class methods
71
+ # Defines klass.define_fields
72
+ # Overrides klass.column_names and klass.define_attribute_methods
73
+ module RecordAttributes
74
+ def define_fields(fields)
75
+ @fields ||= []
76
+
77
+ fields.each do |field|
78
+ field = field.to_sym unless field.is_a?(Symbol)
79
+ @fields << field
80
+ define_method(field) do
81
+ @data[field]
82
+ end
83
+
84
+ # to avoid errors when try saving data
85
+ define_method("#{field}=") do |value|
86
+ @data[field] = value
87
+ end
88
+ end
89
+
90
+ # ActiveRecord make method :id refers to primary key, even there is no column "id"
91
+ if !fields.include?(:id) && !fields.include?("id") && primary_key.present?
92
+ define_method(:id) do
93
+ @data[self.class.primary_key.to_sym]
94
+ end
95
+ end
96
+ end
97
+
98
+ # used in Record#respond_to?
99
+ def define_attribute_methods
100
+ end
101
+
102
+ # Active record keep it as strings, but I keep it as symbols
103
+ def column_names
104
+ @fields.map(&:to_s)
105
+ end
106
+ end
107
+
108
+ # Create LightRecord class based on klass argument
109
+ def base_extended(klass)
110
+ @base_extended ||= {}
111
+ if @base_extended[klass]
112
+ return @base_extended[klass]
113
+ end
114
+
115
+ @base_extended[klass] = LightRecord.build_for_class(klass, klass.column_names)
116
+ end
117
+
118
+ module RelationMethods
119
+
120
+ # Executes query and return array of light object (model class extended by LightRecord)
121
+ def light_records(options = {})
122
+ client = connection.instance_variable_get(:@connection)
123
+ sql = self.to_sql
124
+
125
+ options = {
126
+ stream: false, symbolize_keys: true, cache_rows: false, as: :hash,
127
+ database_timezone: ActiveRecord::Base.default_timezone
128
+ }
129
+ result = _light_record_execute_query(connection, sql, options)
130
+
131
+ klass = LightRecord.build_for_class(self.klass, result.fields)
132
+
133
+ if options[:set_const]
134
+ self.klass.const_set(:"LR_#{Time.now.to_i}", klass)
135
+ end
136
+
137
+ need_symbolize_keys = defined?(PG::Result) && result.is_a?(PG::Result)
138
+
139
+ records = []
140
+ result.each do |row|
141
+ row.symbolize_keys! if need_symbolize_keys
142
+ records << klass.new(row)
143
+ end
144
+
145
+ return records
146
+ end
147
+
148
+ # Same as `#light_records` but iterates through result set and call block for each object
149
+ # this uses less memroy because it creates objects one-by-one
150
+ # it uses stream feature of mysql client
151
+ def light_records_each(options = {})
152
+ conn = ActiveRecord::Base.connection_pool.checkout
153
+ sql = self.to_sql
154
+
155
+ options = {
156
+ stream: true, symbolize_keys: true, cache_rows: false, as: :hash,
157
+ database_timezone: ActiveRecord::Base.default_timezone
158
+ }
159
+ result = _light_record_execute_query(conn, sql, options)
160
+
161
+ klass = LightRecord.build_for_class(self.klass, result.fields)
162
+
163
+ if options[:set_const]
164
+ self.klass.const_set(:"LR_#{Time.now.to_i}", klass)
165
+ end
166
+
167
+ need_symbolize_keys = defined?(PG::Result) && result.is_a?(PG::Result)
168
+
169
+ result.each do |row|
170
+ row.symbolize_keys! if need_symbolize_keys
171
+ yield klass.new(row)
172
+ end
173
+ ensure
174
+ ActiveRecord::Base.connection_pool.checkin(conn)
175
+ end
176
+
177
+ private def _light_record_execute_query(connection, sql, options)
178
+ client = connection.instance_variable_get(:@connection)
179
+
180
+ if client.class.to_s == 'PG::Connection'
181
+ connection.execute(sql, "LightRecord - #{self.klass}")
182
+ else
183
+ connection.send(:log, sql, "LightRecord - #{self.klass}") do
184
+ client.query(sql, options)
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+ ActiveRecord::Relation.send(:include, LightRecord::RelationMethods)
@@ -0,0 +1,117 @@
1
+ require_relative './test_helper'
2
+ require_relative './prepare_db'
3
+
4
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
5
+
6
+ class ARQuestion < ActiveRecord::Base
7
+ self.table_name = "sample"
8
+ self.primary_key = "policy_id"
9
+ end
10
+
11
+ class ARQuestion_wLR < ActiveRecord::Base
12
+ self.table_name = "sample"
13
+ self.primary_key = "policy_id"
14
+
15
+ module LightRecord
16
+ def light_included?
17
+ true
18
+ end
19
+ end
20
+ end
21
+
22
+ describe "LightRecord" do
23
+
24
+ it "should use different connection with #light_records_each" do
25
+ # Should not raise error
26
+ iterated = false
27
+ ARQuestion.all.light_records_each do |sample|
28
+ iterated = true
29
+ ARQuestion.first
30
+ break
31
+ end
32
+
33
+ assert(iterated)
34
+ end
35
+
36
+ it "should create class from AR object" do
37
+ klass = LightRecord.base_extended(ARQuestion)
38
+ assert(klass < ARQuestion)
39
+
40
+ assert_equal(LightRecord.base_extended(ARQuestion).object_id, klass.object_id)
41
+
42
+ assert_equal(klass.column_names, ARQuestion.column_names)
43
+ end
44
+
45
+ it "should be serializable" do
46
+ records = ARQuestion.limit(10).light_records
47
+ assert_equal(records.to_json, ARQuestion.limit(10).to_a.to_json)
48
+ end
49
+
50
+ it "should be serializable" do
51
+ light = ARQuestion.limit(1).light_records.first
52
+ dark = ARQuestion.find(light.id)
53
+
54
+ assert_equal(light.attributes, dark.attributes.symbolize_keys)
55
+ end
56
+
57
+ it "should include LightRecord submodule if present" do
58
+ record = ARQuestion_wLR.limit(1).light_records.first
59
+ assert(record.light_included?)
60
+
61
+ klass = LightRecord.base_extended(ARQuestion_wLR)
62
+ assert_includes(klass.ancestors, ARQuestion_wLR::LightRecord)
63
+ end
64
+
65
+ it "should work with #respond_to?" do
66
+ rand_sql_fn = ENV['DB'] == 'postgres' ? "random" : "rand"
67
+ light = ARQuestion.select("*, #{rand_sql_fn}() as extra_column_from_sql").limit(1).light_records.first
68
+
69
+ assert_respond_to(light, :extra_column_from_sql)
70
+ ARQuestion.column_names.each do |column_name|
71
+ assert_respond_to(light, column_name)
72
+ end
73
+ end
74
+
75
+ it "sgould have primary key" do
76
+ light = ARQuestion.limit(1).light_records.first
77
+ dark = ARQuestion.find(light.id)
78
+
79
+ assert_equal(light.id, light[ARQuestion.primary_key])
80
+ assert_equal(light.id, dark.id)
81
+ end
82
+
83
+ it "should be read-only" do
84
+ record = ARQuestion_wLR.limit(1).light_records.first
85
+
86
+ assert(record.readonly?)
87
+
88
+ assert_raises(ActiveRecord::ReadOnlyRecord) do
89
+ record.update_attributes(ARQuestion.column_names.first => "bla bla lba")
90
+ end
91
+ end
92
+
93
+ it "should be not as a new record" do
94
+ record = ARQuestion_wLR.limit(1).light_records.first
95
+ refute(record.new_record?)
96
+ end
97
+
98
+ it "should add extra params in query" do
99
+ record = ARQuestion_wLR.where(policy_id: 119736).light_records.first
100
+ assert_equal(record.id, 119736)
101
+ end
102
+
103
+ if ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
104
+ it "should select datetime in UTC" do
105
+ # Active Record will set :database_timezone on first query
106
+ # But if no query run on that connection then it will be blank
107
+ # This code is simulating that behaviour
108
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
109
+ client = ActiveRecord::Base.connection.instance_variable_get(:@connection)
110
+ client.query_options.delete(:database_timezone)
111
+ end
112
+
113
+ record = ARQuestion_wLR.select("now() as time").light_records.first
114
+ assert(record.time.gmt?, "Time is not UTC")
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,132 @@
1
+ require "mysql2"
2
+ require "active_record"
3
+
4
+ if ENV['DB'] == 'postgres'
5
+ ActiveRecord::Base.establish_connection(
6
+ host: '127.0.0.1',
7
+ adapter: 'postgresql',
8
+ encoding: 'unicode',
9
+ database: 'light_record',
10
+ pool: 5,
11
+ username: ENV['DB_USER'] || ENV["USER"],
12
+ password: '',
13
+ reconnect: true
14
+ )
15
+ else
16
+ ActiveRecord::Base.establish_connection(
17
+ adapter: 'mysql2',
18
+ database: 'light_record',
19
+ host: 'localhost',
20
+ username: 'root',
21
+ password: '',
22
+ pool: 5
23
+ )
24
+ end
25
+
26
+ require 'active_record/connection_adapters/mysql2_adapter'
27
+ require 'active_record/connection_adapters/postgresql_adapter'
28
+
29
+ module TestDB
30
+
31
+ SAMPLE_TABLE = 'sample'
32
+
33
+ def self.init
34
+ db = ActiveRecord::Base.connection
35
+
36
+ if db.data_source_exists?(SAMPLE_TABLE)
37
+ puts "Testing table already exists"
38
+ return
39
+ end
40
+
41
+ if db.is_a? ActiveRecord::ConnectionAdapters::Mysql2Adapter
42
+ db.execute(%{
43
+ CREATE TABLE `#{SAMPLE_TABLE}` (
44
+ `policy_id` int(11) NOT NULL,
45
+ `statecode` varchar(255) DEFAULT NULL,
46
+ `county` varchar(255) DEFAULT NULL,
47
+ `eq_site_limit` varchar(255) DEFAULT NULL,
48
+ `hu_site_limit` varchar(255) DEFAULT NULL,
49
+ `fl_site_limit` varchar(255) DEFAULT NULL,
50
+ `fr_site_limit` varchar(255) DEFAULT NULL,
51
+ `tiv_2011` varchar(255) DEFAULT NULL,
52
+ `tiv_2012` varchar(255) DEFAULT NULL,
53
+ `eq_site_deductible` float DEFAULT NULL,
54
+ `hu_site_deductible` varchar(255) DEFAULT NULL,
55
+ `fl_site_deductible` int(11) DEFAULT NULL,
56
+ `fr_site_deductible` int(11) DEFAULT NULL,
57
+ `point_latitude` varchar(255) DEFAULT NULL,
58
+ `point_longitude` varchar(255) DEFAULT NULL,
59
+ `line` varchar(255) DEFAULT NULL,
60
+ `construction` varchar(255) DEFAULT NULL,
61
+ `point_granularity` int(11) DEFAULT NULL,
62
+ PRIMARY KEY (`policy_id`)
63
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
64
+ })
65
+ elsif db.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
66
+ db.execute(%{
67
+ CREATE TABLE #{SAMPLE_TABLE} (
68
+ policy_id integer NULL,
69
+ statecode varchar(255) NULL,
70
+ county varchar(255) NULL,
71
+ eq_site_limit varchar(255) NULL,
72
+ hu_site_limit varchar(255) NULL,
73
+ fl_site_limit varchar(255) NULL,
74
+ fr_site_limit varchar(255) NULL,
75
+ tiv_2011 varchar(255) NULL,
76
+ tiv_2012 varchar(255) NULL,
77
+ eq_site_deductible real NULL,
78
+ hu_site_deductible varchar(255) NULL,
79
+ fl_site_deductible real NULL,
80
+ fr_site_deductible real NULL,
81
+ point_latitude varchar(255) NULL,
82
+ point_longitude varchar(255) NULL,
83
+ line varchar(255) DEFAULT NULL,
84
+ construction varchar(255) NULL,
85
+ point_granularity real NULL,
86
+ PRIMARY KEY(policy_id)
87
+ );
88
+ })
89
+ end
90
+
91
+ import_csv(db)
92
+
93
+ ensure
94
+ #db.execute(%{drop table #{SAMPLE_TABLE}})
95
+ end
96
+
97
+ def self.import_csv(db)
98
+ require 'csv'
99
+
100
+ index = 0
101
+ headers = nil
102
+ rows_to_insert = []
103
+
104
+ puts "Importing testing data"
105
+
106
+ CSV.foreach(File.dirname(__FILE__) + "/../test_data/FL_insurance_sample.csv", headers: true) do |row|
107
+ headers ||= row.headers
108
+
109
+ row_values = []
110
+ headers.size.times do |i|
111
+ row_values << %{'#{db.quote_string(row.field(i))}'}
112
+ end
113
+
114
+ rows_to_insert << row_values
115
+
116
+ if index % 3000 == 0 && index > 0 || index == 36633
117
+ puts "Row #{index}"
118
+
119
+ values = rows_to_insert.map {|r| '(' + r.join(", ") + ')' }.join(', ')
120
+
121
+ db.execute(%{
122
+ insert into #{SAMPLE_TABLE} (#{headers.join(", ")}) values #{values}
123
+ })
124
+ rows_to_insert.clear
125
+ end
126
+
127
+ index += 1
128
+ end
129
+ end
130
+ end
131
+
132
+ TestDB.init
@@ -0,0 +1,17 @@
1
+ require 'bundler/setup'
2
+ require "minitest/reporters"
3
+
4
+ Minitest::Reporters.use!(
5
+ Minitest::Reporters::DefaultReporter.new(color: true)
6
+ )
7
+
8
+ def MiniTest.filter_backtrace(bt)
9
+ bt
10
+ end
11
+
12
+ require "mysql2"
13
+ require "active_record"
14
+ require 'light_record'
15
+ require 'looksee'
16
+
17
+ require "minitest/autorun"
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: light_record
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Pavel Evstigneev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - pavel.evst@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/light_record.rb
21
+ - spec/light_record_spec.rb
22
+ - spec/prepare_db.rb
23
+ - spec/test_helper.rb
24
+ homepage: http://github.com/paxa/light_record
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.5.1
45
+ signing_key:
46
+ specification_version: 3
47
+ summary: Getting process memory
48
+ test_files:
49
+ - spec/light_record_spec.rb
50
+ - spec/prepare_db.rb
51
+ - spec/test_helper.rb