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.
@@ -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
- # Overwriting ActiveRecord::Relation to make opt_ar_objects
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
- # TODO: Introduce pluck usage for relations
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 opt_ar_objects method available
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
- def opt_ar_objects(options = {})
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 opt_ar_objects for hash
31
+ # TODO: Implement optars for hash
46
32
  # class Hash
47
- # def opt_ar_objects
33
+ # def optars
48
34
 
49
35
  # end
50
36
  # end
@@ -1,7 +1,47 @@
1
1
  module ActiveRecord
2
- class Relation
3
- def opt_ar_objects(options = {})
4
- to_a.opt_ar_objects(options)
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 Array
2
- def opt_ar_objects(options = {})
3
- map { |obj| obj.opt_ar_object(options) }
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
@@ -23,5 +23,11 @@ module OptAR
23
23
 
24
24
  class TimeTypeExpectedError < StandardError
25
25
  end
26
+
27
+ class MandatoryPrimaryKeyMissingError < StandardError
28
+ end
29
+
30
+ class UnknownARColumnError < StandardError
31
+ end
26
32
  end
27
33
  end
@@ -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[:req_attributes] || []
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
- attribute_keys = req_attributes +
54
- mandatory_attributes(klass) -
55
- skipped_attributes(klass)
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 build_opt_ar(name, options = {})
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
- options = fetch_options(options, *args)
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 show_as build_opt_ar
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 !name.is_a?(Symbol)
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).opt_ar_objects(options)
55
+ send(scope).optars(options)
67
56
  end
68
57
 
69
58
  def fetch_default_scoped_optars(options)
70
- send(:build_default_scope).opt_ar_objects(options)
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 = " :: show_as defined with Undefined Scope :: #{options[:scope]}"
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 = " :: show_as defined with invalid name :: #{error_options}"
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 opt_ar_object(options = {})
81
+ def optar(options = {})
93
82
  OptAR::OAR.new(self, options)
94
83
  end
95
84
 
96
- alias opt_ar_objects opt_ar_object
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)
@@ -1,3 +1,3 @@
1
1
  module OptAR
2
- VERSION = '1.0.1'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
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.1
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-04 00:00:00.000000000 Z
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