light_record 0.2

Sign up to get free protection for your applications and to get access to all the features.
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