opt_ar 1.0.1 → 1.1.0
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 +4 -4
- data/.circleci/config.yml +51 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +2 -0
- data/Gemfile +13 -3
- data/Gemfile.lock +67 -19
- data/README.md +141 -7
- data/Rakefile +22 -0
- data/benchmarks/benchmark_1.rb +131 -0
- data/benchmarks/benchmark_2.rb +100 -0
- data/benchmarks/benchmark_3.rb +96 -0
- data/benchmarks/benchmark_4.rb +101 -0
- data/benchmarks/benchmark_5.rb +108 -0
- data/benchmarks/benchmark_6.rb +60 -0
- data/benchmarks/employee_creation.rb +16 -0
- data/lib/opt_ar.rb +9 -23
- data/lib/opt_ar/core_ext/active_record/relation.rb +43 -3
- data/lib/opt_ar/core_ext/array.rb +13 -3
- data/lib/opt_ar/errors.rb +6 -0
- data/lib/opt_ar/helpers/method_finder_helper.rb +3 -0
- data/lib/opt_ar/oar.rb +22 -4
- data/lib/opt_ar/optimal_ar/builder.rb +13 -22
- data/lib/opt_ar/version.rb +1 -1
- data/opt_ar.gemspec +4 -1
- metadata +13 -3
@@ -0,0 +1,16 @@
|
|
1
|
+
10_000.times do |n|
|
2
|
+
time = (n % 3_000).days.ago
|
3
|
+
time1 = ((n + rand(32)) % 3_000).days.ago
|
4
|
+
e = Employee.new(
|
5
|
+
emp_id: (13_001 + n),
|
6
|
+
first_name: (0...6).map { ('a'..'z').to_a[rand(26)] }.join,
|
7
|
+
last_name: (0...6).map { ('a'..'z').to_a[rand(26)] }.join,
|
8
|
+
birth_date: time1.to_date,
|
9
|
+
hire_date: time.to_date,
|
10
|
+
created_at: time,
|
11
|
+
updated_at: time + 1000,
|
12
|
+
gender: n % 2,
|
13
|
+
password: (0...9).map { ('a'..'z').to_a[rand(26)] }.join
|
14
|
+
)
|
15
|
+
e.save
|
16
|
+
end
|
data/lib/opt_ar.rb
CHANGED
@@ -3,6 +3,9 @@ require 'active_record'
|
|
3
3
|
require_relative './opt_ar/optimal_ar/builder'
|
4
4
|
require_relative 'opt_ar/logger'
|
5
5
|
require_relative 'opt_ar/errors'
|
6
|
+
require_relative 'opt_ar/core_ext/active_record/relation'
|
7
|
+
require_relative 'opt_ar/core_ext/array'
|
8
|
+
# Dir['/opt_ar/core_ext/*/*.rb'].each { |file| require file }
|
6
9
|
# Dir["#{File.dirname(__FILE__)}/lib/opt_ar/**/*.rb"].each { |f| require f }
|
7
10
|
|
8
11
|
# Base module
|
@@ -12,39 +15,22 @@ end
|
|
12
15
|
ActiveRecord::Base.send :include, OptAR::OptimalAR::Builder
|
13
16
|
|
14
17
|
module ActiveRecord
|
15
|
-
#
|
16
|
-
# method available for all relations, returning array of
|
17
|
-
# `OAR`s which we generate
|
18
|
+
# Extending ActiveRecord::Relation to respond to optars
|
18
19
|
class Relation
|
19
|
-
|
20
|
-
# if ActiveRecord::VERSION::MAJOR < 4
|
21
|
-
# to_a.opt_ar_objects(options)
|
22
|
-
# else
|
23
|
-
# pluck(options[:req_attributes]).opt_ar_objects
|
24
|
-
# end
|
25
|
-
def opt_ar_objects(options = {})
|
26
|
-
to_a.opt_ar_objects(options)
|
27
|
-
end
|
20
|
+
include ActiveRecord::RelationExtender
|
28
21
|
end
|
29
22
|
end
|
30
23
|
|
31
|
-
# Overwriting Array to make
|
24
|
+
# Overwriting Array to make optars method available
|
32
25
|
# for all arrays containing ActiveRecord::Base objects, returning
|
33
26
|
# an array of `OAR`s which we generate
|
34
27
|
class Array
|
35
|
-
|
36
|
-
map do |obj|
|
37
|
-
unless obj.is_a? ActiveRecord::Base
|
38
|
-
raise OptAR::Errors::NonActiveRecordError
|
39
|
-
end
|
40
|
-
obj.opt_ar_object(options)
|
41
|
-
end
|
42
|
-
end
|
28
|
+
include ArrayExtender
|
43
29
|
end
|
44
30
|
|
45
|
-
# TODO: Implement
|
31
|
+
# TODO: Implement optars for hash
|
46
32
|
# class Hash
|
47
|
-
# def
|
33
|
+
# def optars
|
48
34
|
|
49
35
|
# end
|
50
36
|
# end
|
@@ -1,7 +1,47 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
# Adding functionality to ActiveRecord::Relation to make
|
3
|
+
# optars method available for all relations,
|
4
|
+
# returning array of `OAR`s which we generate
|
5
|
+
module RelationExtender
|
6
|
+
DEFAULT_QUERY_OPTIONS = {
|
7
|
+
symbolize_keys: true, # Returns attributes with symbolized keys
|
8
|
+
as: :hash, # Returns rows as Hash objects
|
9
|
+
cast_booleans: true, # Typecasts TINYINT columns
|
10
|
+
database_timezone: ActiveRecord::Base.default_timezone,
|
11
|
+
cache_rows: false
|
12
|
+
# Disabled because iteration happens only once allowing GC to cleanup
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def optars(options = {})
|
16
|
+
attrs = options[:attrs] || []
|
17
|
+
validate_columns(attrs)
|
18
|
+
select_attrs = ([primary_key] + attrs).uniq
|
19
|
+
sql_query = except(:select).select(select_attrs).to_sql
|
20
|
+
fetch_objects(sql_query, select_attrs)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias opt_ar_objects optars
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def fetch_objects(query, requested_attributes)
|
28
|
+
res = fetch_sql_rows(query, DEFAULT_QUERY_OPTIONS)
|
29
|
+
res.map do |row|
|
30
|
+
OptAR::OAR.init_manual(row, klass, requested_attributes)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_sql_rows(query, options)
|
35
|
+
client = connection.raw_connection
|
36
|
+
connection.send(:log, query, "OptAR - #{klass.name}") do
|
37
|
+
client.query(query, options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_columns(attrs)
|
42
|
+
klass_attrs = klass.column_names.map(&:to_sym)
|
43
|
+
valid = (attrs - klass_attrs).empty?
|
44
|
+
raise OptAR::Errors::UnknownARColumnError unless valid
|
5
45
|
end
|
6
46
|
end
|
7
47
|
end
|
@@ -1,5 +1,15 @@
|
|
1
|
-
class
|
2
|
-
|
3
|
-
|
1
|
+
# Extender to add functionality to Array class to make optars
|
2
|
+
# method available for an array of ActiveRecord objects,
|
3
|
+
# returning array of `OAR`s which we generate
|
4
|
+
module ArrayExtender
|
5
|
+
def optars(options = {})
|
6
|
+
map do |obj|
|
7
|
+
unless obj.is_a? ActiveRecord::Base
|
8
|
+
raise OptAR::Errors::NonActiveRecordError
|
9
|
+
end
|
10
|
+
obj.optar(options)
|
11
|
+
end
|
4
12
|
end
|
13
|
+
|
14
|
+
alias opt_ar_objects optars
|
5
15
|
end
|
data/lib/opt_ar/errors.rb
CHANGED
@@ -42,6 +42,9 @@ module OptAR
|
|
42
42
|
# Be cautious of using respond_to? as this will try to query for the
|
43
43
|
# actual ActiveRecord object and check for it's response
|
44
44
|
def respond_to_missing?(*args)
|
45
|
+
method_name = args[0]
|
46
|
+
return true if @attributes.key?(method_name)
|
47
|
+
|
45
48
|
load_ar_object
|
46
49
|
@klass_object.respond_to?(*args)
|
47
50
|
end
|
data/lib/opt_ar/oar.rb
CHANGED
@@ -16,12 +16,22 @@ module OptAR
|
|
16
16
|
BLACKLISTED_ATTRIBUTES = 'BLACKLISTED_ATTRIBUTES'.freeze
|
17
17
|
|
18
18
|
def initialize(object, options = {})
|
19
|
-
req_attributes = options[:
|
19
|
+
req_attributes = options[:attrs] || []
|
20
20
|
assign_attributes(object, req_attributes)
|
21
21
|
@klass_name = object.class.name
|
22
22
|
# define_attr_readers
|
23
23
|
end
|
24
24
|
|
25
|
+
def self.init_manual(attrs, klass, req_attributes)
|
26
|
+
oar = allocate
|
27
|
+
attr_keys = oar.send(:fetch_attribute_keys, req_attributes, klass)
|
28
|
+
attrs = attrs.slice(*attr_keys)
|
29
|
+
oar.instance_variable_set('@attributes', attrs)
|
30
|
+
oar.instance_variable_set('@klass_name', klass.name)
|
31
|
+
oar.send(:transform_datetime_attributes, klass)
|
32
|
+
oar
|
33
|
+
end
|
34
|
+
|
25
35
|
def as_json
|
26
36
|
{
|
27
37
|
klass_key => attributes.as_json
|
@@ -50,13 +60,21 @@ module OptAR
|
|
50
60
|
def assign_attributes(object, req_attributes)
|
51
61
|
obj_attributes = object.attributes.symbolize_keys
|
52
62
|
klass = object.class
|
53
|
-
|
54
|
-
|
55
|
-
|
63
|
+
p_key = klass_primary_key(klass)
|
64
|
+
|
65
|
+
unless obj_attributes[p_key]
|
66
|
+
raise OptAR::Errors::MandatoryPrimaryKeyMissingError
|
67
|
+
end
|
68
|
+
|
69
|
+
attribute_keys = fetch_attribute_keys(req_attributes, klass)
|
56
70
|
@attributes = obj_attributes.slice(*attribute_keys)
|
57
71
|
transform_datetime_attributes(object.class)
|
58
72
|
end
|
59
73
|
|
74
|
+
def fetch_attribute_keys(req_attributes, klass)
|
75
|
+
req_attributes + mandatory_attributes(klass) - skipped_attributes(klass)
|
76
|
+
end
|
77
|
+
|
60
78
|
def mandatory_attributes(klass)
|
61
79
|
[klass_primary_key(klass)]
|
62
80
|
end
|
@@ -5,18 +5,17 @@ module OptAR
|
|
5
5
|
module OptimalAR
|
6
6
|
module Builder
|
7
7
|
module ClassMethods
|
8
|
-
def
|
8
|
+
def build_optar(name, options = {})
|
9
9
|
validate_name(name)
|
10
10
|
validate_scope(options[:scope]) if options[:scope]
|
11
11
|
faker_proc = lambda do |*args|
|
12
|
-
|
13
|
-
scope = options[:scope]
|
14
|
-
fetch_optar_objects(scope, options)
|
12
|
+
fetch_optar_objects(options[:scope], options)
|
15
13
|
end
|
16
14
|
singleton_class.send(:redefine_method, name, &faker_proc)
|
17
15
|
end
|
18
16
|
|
19
|
-
alias
|
17
|
+
alias swindle build_optar
|
18
|
+
alias show_as build_optar
|
20
19
|
|
21
20
|
private
|
22
21
|
|
@@ -37,23 +36,13 @@ module OptAR
|
|
37
36
|
end
|
38
37
|
|
39
38
|
def valid_name?(name)
|
40
|
-
if
|
41
|
-
:symbol_expected
|
42
|
-
elsif respond_to? name
|
39
|
+
if respond_to? name
|
43
40
|
:duplicate_name
|
44
41
|
else
|
45
42
|
true
|
46
43
|
end
|
47
44
|
end
|
48
45
|
|
49
|
-
def fetch_options(options, *args)
|
50
|
-
if options.respond_to?(:call)
|
51
|
-
unscoped { options.call(*args) }
|
52
|
-
else
|
53
|
-
options
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
46
|
def fetch_optar_objects(scope, options)
|
58
47
|
if scope
|
59
48
|
fetch_scoped_optar(scope, options)
|
@@ -63,11 +52,11 @@ module OptAR
|
|
63
52
|
end
|
64
53
|
|
65
54
|
def fetch_scoped_optar(scope, options)
|
66
|
-
send(scope).
|
55
|
+
send(scope).optars(options)
|
67
56
|
end
|
68
57
|
|
69
58
|
def fetch_default_scoped_optars(options)
|
70
|
-
send(:build_default_scope).
|
59
|
+
send(:build_default_scope).optars(options)
|
71
60
|
end
|
72
61
|
|
73
62
|
def throw_error(error_type, error_options)
|
@@ -75,25 +64,27 @@ module OptAR
|
|
75
64
|
end
|
76
65
|
|
77
66
|
def undefined_scope(options)
|
78
|
-
msg = " ::
|
67
|
+
msg = " :: swindle defined with Undefined Scope :: #{options[:scope]}"
|
79
68
|
OptAR::Logger.log(msg, :error)
|
80
69
|
OptAR::Errors::UndefinedScopeError.new(options)
|
81
70
|
end
|
82
71
|
|
83
72
|
def invalid_name(options)
|
84
73
|
error_options = "#{options[:name]} :: #{options[:type]}"
|
85
|
-
msg = " ::
|
74
|
+
msg = " :: swindle defined with invalid name :: #{error_options}"
|
86
75
|
OptAR::Logger.log(msg, :error)
|
87
76
|
OptAR::Errors::DuplicateNameError.new(options)
|
88
77
|
end
|
89
78
|
end
|
90
79
|
|
91
80
|
module InstanceMethods
|
92
|
-
def
|
81
|
+
def optar(options = {})
|
93
82
|
OptAR::OAR.new(self, options)
|
94
83
|
end
|
95
84
|
|
96
|
-
alias
|
85
|
+
alias optars optar
|
86
|
+
alias opt_ar_objects optar
|
87
|
+
alias opt_ar_object optar
|
97
88
|
end
|
98
89
|
|
99
90
|
def self.included(receiver)
|
data/lib/opt_ar/version.rb
CHANGED
data/opt_ar.gemspec
CHANGED
@@ -17,7 +17,10 @@ Gem::Specification.new do |spec|
|
|
17
17
|
'them just as you would on an AR, for better memory '\
|
18
18
|
'optimization. Ideally, suitable in place of caching '\
|
19
19
|
'AR objects with cache stores like Memcached, where '\
|
20
|
-
'serialization and de-serialization are memory-hungry'
|
20
|
+
'serialization and de-serialization are memory-hungry. '\
|
21
|
+
'Optars can save upto 90% of your memory(object '\
|
22
|
+
'allocations), while being upto 20x faster, when '\
|
23
|
+
'fetching huge AR results.'
|
21
24
|
spec.homepage = 'https://github.com/ragav0102/opt_ar'
|
22
25
|
spec.license = 'MIT'
|
23
26
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opt_ar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ragav0102
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-05-
|
11
|
+
date: 2019-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -84,13 +84,16 @@ description: Generates memory-optimal immutable ActiveRecord dupes that are easi
|
|
84
84
|
serializable and behaves much like ARs. Define required attributes before-hand and
|
85
85
|
use them just as you would on an AR, for better memory optimization. Ideally, suitable
|
86
86
|
in place of caching AR objects with cache stores like Memcached, where serialization
|
87
|
-
and de-serialization are memory-hungry
|
87
|
+
and de-serialization are memory-hungry. Optars can save upto 90% of your memory(object
|
88
|
+
allocations), while being upto 20x faster, when fetching huge AR results.
|
88
89
|
email:
|
89
90
|
- ragavh123@gmail.com
|
90
91
|
executables: []
|
91
92
|
extensions: []
|
92
93
|
extra_rdoc_files: []
|
93
94
|
files:
|
95
|
+
- ".circleci/config.yml"
|
96
|
+
- ".coveralls.yml"
|
94
97
|
- ".gitignore"
|
95
98
|
- ".travis.yml"
|
96
99
|
- CODE_OF_CONDUCT.md
|
@@ -99,6 +102,13 @@ files:
|
|
99
102
|
- LICENSE.txt
|
100
103
|
- README.md
|
101
104
|
- Rakefile
|
105
|
+
- benchmarks/benchmark_1.rb
|
106
|
+
- benchmarks/benchmark_2.rb
|
107
|
+
- benchmarks/benchmark_3.rb
|
108
|
+
- benchmarks/benchmark_4.rb
|
109
|
+
- benchmarks/benchmark_5.rb
|
110
|
+
- benchmarks/benchmark_6.rb
|
111
|
+
- benchmarks/employee_creation.rb
|
102
112
|
- bin/console
|
103
113
|
- bin/setup
|
104
114
|
- lib/opt_ar.rb
|