opt_ar 1.0.0 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93053f9644941d1687edc76dec02f9d2dfb64a50f4f0931c4df50b5d83dee276
4
- data.tar.gz: 138d399b11b12eac779bb7533c170a1110f8b39c9258cbae618411b5f0ec4441
3
+ metadata.gz: 05daa8fd91d2473f6cf34e98d2e5ac791f920894ba9320494acf5eda3f8ca0eb
4
+ data.tar.gz: bfa798775b961b5d4d33c83fd78ca53fef8a7535903e0930c802793b1598f3a8
5
5
  SHA512:
6
- metadata.gz: 8b8365b6c9e4faf4569c50a76a51403891555b1784546bb67ef051bd3e72eae0ae8002900046c00acbecdeffa4b825f1e6a732480ad1165faff937ff30a0e859
7
- data.tar.gz: 69dd6621084a08c33aad785459eb2a44ce2b9b62ce04cd1916e7247312f40b7ce1b3c24200ed3d6eebfb287a790ea9d640dc855ff929f9192cd82eaaecfc1a08
6
+ metadata.gz: ea64ea8e8691073516396dd1ac53bd90b08c1a1fab8baeaf80d8c3dad2de6bc83f4f5128328246617052f2761755c5b26ef2c2a0f1e0a3317b547ef93dc21944
7
+ data.tar.gz: 56d26b34be8033148c3452bd77e1177ab80eeb6247dc88ba4a45329e522f86bf76d719ba67ef997abe1471d0916add31fef4231107f93b20e9eddf84d3790ffc
data/.gitignore CHANGED
@@ -6,3 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .byebug_history
10
+ /logs/
data/Gemfile CHANGED
@@ -1,10 +1,16 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in opt_ar.gemspec
6
6
  gemspec
7
7
 
8
8
  gem 'activerecord', '>= 3.2'
9
- gem 'mysql2', '>= 0.3 '
10
- gem 'minitest-reporters'
9
+
10
+ # gem 'byebug', '10.0.2'
11
+
12
+ # gem 'minitest-reporters'
13
+ # gem 'minitest-stub-const'
14
+ # gem 'minitest-logger'
15
+
16
+ gem 'mysql2', '>= 0.3'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opt_ar (1.0.0)
4
+ opt_ar (1.0.1)
5
5
  activerecord (>= 3.2)
6
6
  mysql2 (>= 0.3)
7
7
 
@@ -21,20 +21,13 @@ GEM
21
21
  minitest (~> 5.1)
22
22
  thread_safe (~> 0.3, >= 0.3.4)
23
23
  tzinfo (~> 1.1)
24
- ansi (1.5.0)
25
24
  arel (6.0.4)
26
25
  builder (3.2.3)
27
26
  i18n (0.8.1)
28
27
  json (1.8.6)
29
28
  minitest (5.10.1)
30
- minitest-reporters (1.3.5)
31
- ansi
32
- builder
33
- minitest (>= 5.0)
34
- ruby-progressbar
35
29
  mysql2 (0.4.9)
36
30
  rake (10.4.2)
37
- ruby-progressbar (1.10.0)
38
31
  thread_safe (0.3.6)
39
32
  tzinfo (1.2.2)
40
33
  thread_safe (~> 0.1)
@@ -46,7 +39,6 @@ DEPENDENCIES
46
39
  activerecord (>= 3.2)
47
40
  bundler (~> 1.16)
48
41
  minitest (~> 5.0)
49
- minitest-reporters
50
42
  mysql2 (>= 0.3)
51
43
  opt_ar!
52
44
  rake (~> 10.0)
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/testtask'
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << 'test'
6
6
  t.libs << 'lib'
7
- t.test_files = FileList['test/**/*_test.rb']
7
+ t.test_files = FileList['test/*_test.rb']
8
8
  end
9
9
 
10
10
  task default: :test
data/bin/console CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "opt_ar"
2
+ require 'bundler/setup'
3
+ require 'opt_ar'
4
+ require 'mysql2'
5
+ require 'active_record'
6
+ require 'active_record/connection_adapters/mysql2_adapter'
7
+ require_relative '../test/models/test_models'
5
8
 
6
9
  # You can add fixtures and/or initialization code here to make experimenting
7
10
  # with your gem easier. You can also use a different console, if you like.
@@ -9,6 +12,19 @@ require "opt_ar"
9
12
  # (If you use this, don't forget to add pry to your Gemfile!)
10
13
  # require "pry"
11
14
  # Pry.start
15
+ require 'irb'
16
+
17
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
18
+
19
+ ActiveRecord::Base.establish_connection(
20
+ adapter: 'mysql2',
21
+ database: 'optar_test',
22
+ host: 'localhost',
23
+ username: 'root',
24
+ password: '',
25
+ pool: 5
26
+ )
27
+
28
+ # ActiveRecord::Base.connection_pool.clear_reloadable_connections!
12
29
 
13
- require "irb"
14
30
  IRB.start(__FILE__)
data/lib/opt_ar/errors.rb CHANGED
@@ -14,5 +14,14 @@ module OptAR
14
14
 
15
15
  class MissingPrimaryKeyError < StandardError
16
16
  end
17
+
18
+ class NonActiveRecordError < StandardError
19
+ end
20
+
21
+ class PrimaryKeyBlacklistedError < StandardError
22
+ end
23
+
24
+ class TimeTypeExpectedError < StandardError
25
+ end
17
26
  end
18
27
  end
@@ -1,7 +1,10 @@
1
1
  module OptAR
2
+ # Methods for handling all attributes read
2
3
  module MethodFinderHelper
3
4
  ID_STRING = 'id'.freeze
4
5
  WARN_MSG = 'WARNING :: Trying to access attr that was not requested'.freeze
6
+ DEFAULT_DATE_TIME_ATTRIBUTES = %i[created_at updated_at].freeze
7
+ DATE_TIME_CONST_KEY = 'DATE_TIME_ATTRIBUTES'.freeze
5
8
 
6
9
  private
7
10
 
@@ -22,13 +25,14 @@ module OptAR
22
25
  # methods on the instance, for attribute that was not requested initially
23
26
  def method_missing(method_name, *args)
24
27
  method_name = method_name.to_sym
25
- return @attributes[method_name] if @attributes.key?(method_name)
28
+ return read_from_attributes(method_name) if @attributes.key?(method_name)
26
29
 
27
30
  raise OptAR::Errors::MissingPrimaryKeyError if primary_key?(method_name)
28
31
 
29
32
  raise OptAR::Errors::MutationNotAllowedError if assign?(method_name)
30
33
 
31
- Rails.logger.warn "#{WARN_MSG} :: #{method_name}"
34
+ OptAR::Logger.log("#{WARN_MSG} :: #{method_name}", :warn)
35
+
32
36
  load_ar_object
33
37
  @klass_object ? @klass_object.send(method_name, *args) : super
34
38
  end
@@ -42,24 +46,55 @@ module OptAR
42
46
  @klass_object.respond_to?(*args)
43
47
  end
44
48
 
49
+ def read_from_attributes(key)
50
+ klass = klass_name.constantize
51
+ if transformable_fields(klass).include?(key)
52
+ Time.at(attributes[key]).utc
53
+ else
54
+ attributes[key]
55
+ end
56
+ end
57
+
45
58
  # Default scope for the class defined by klass_name will be applied here
46
59
  def load_ar_object
47
- @klass_object ||= klass_name.constantize
48
- .where(id: id)
49
- .first(readonly: true)
60
+ @klass_object ||= begin
61
+ klass_name.constantize
62
+ .readonly(true)
63
+ .where("#{klass_primary_key}=#{klass_primary_key_value}")
64
+ .first
65
+ end
50
66
  raise OptAR::Errors::ARObjectNotFoundError unless @klass_object
51
67
  end
52
68
 
69
+ def transformable_fields(klass)
70
+ @transformable_keys ||= begin
71
+ keys = attributes.keys
72
+ attrs = if klass.const_defined?(DATE_TIME_CONST_KEY)
73
+ klass.const_get(DATE_TIME_CONST_KEY)
74
+ else
75
+ DEFAULT_DATE_TIME_ATTRIBUTES
76
+ end
77
+ keys & attrs
78
+ end
79
+ end
80
+
53
81
  def klass_key
54
82
  klass_name.gsub('::', '_').underscore
55
83
  end
56
84
 
57
- def klass_primary_key(klass)
58
- klass.primary_key.to_sym
85
+ def klass_primary_key(klass = nil)
86
+ @primary_key ||= begin
87
+ klass ||= klass_name.constantize
88
+ klass.primary_key.to_sym
89
+ end
90
+ end
91
+
92
+ def klass_primary_key_value
93
+ attributes[klass_primary_key]
59
94
  end
60
95
 
61
96
  def primary_key?(method_name)
62
- method_name == klass_primary_key(klass_name.constantize)
97
+ method_name == klass_primary_key
63
98
  end
64
99
 
65
100
  def assign?(method_name)
@@ -0,0 +1,17 @@
1
+ module OptAR
2
+ # Log implementation for the gem
3
+ #
4
+ # Tries to use ActiveRecord::Base.logger which is compatible with rails
5
+ # else, fallbacks to STDOUT
6
+ module Logger
7
+ class << self
8
+ def log(msg, level = :info)
9
+ logger.send(level, msg)
10
+ end
11
+
12
+ def logger
13
+ ActiveRecord::Base.logger || ::Logger.new(STDOUT)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,14 @@
1
1
  require File.dirname(__FILE__) + '/helpers/method_finder_helper'
2
2
 
3
3
  module OptAR
4
- class OptimalActiveRecord
4
+ # Dupe of ActiveRecord::Base providing necessary read functionalities
5
+ # of it, while removing a lot of abstactions that it provides
6
+ # to reduce memory and processing time utilisation
7
+ #
8
+ # attributes : contains the requested attributes from AR
9
+ # klass_name : class name of original AR model
10
+ # klass_object : original AR object loaded
11
+ class OAR
5
12
  include OptAR::MethodFinderHelper
6
13
 
7
14
  attr_reader :attributes, :klass_name, :klass_object
@@ -47,6 +54,7 @@ module OptAR
47
54
  mandatory_attributes(klass) -
48
55
  skipped_attributes(klass)
49
56
  @attributes = obj_attributes.slice(*attribute_keys)
57
+ transform_datetime_attributes(object.class)
50
58
  end
51
59
 
52
60
  def mandatory_attributes(klass)
@@ -59,12 +67,28 @@ module OptAR
59
67
  # security related information to the outside world
60
68
  def skipped_attributes(klass)
61
69
  if klass.const_defined?(BLACKLISTED_ATTRIBUTES)
62
- klass.const_get(BLACKLISTED_ATTRIBUTES)
70
+ attrs = klass.const_get(BLACKLISTED_ATTRIBUTES)
71
+ if attrs.include? klass_primary_key(klass)
72
+ raise OptAR::Errors::PrimaryKeyBlacklistedError
73
+ end
74
+ attrs
63
75
  else
64
76
  []
65
77
  end
66
78
  end
67
79
 
80
+ def transform_datetime_attributes(klass)
81
+ fields = transformable_fields(klass)
82
+ return unless fields.present?
83
+ fields.each do |field|
84
+ val = attributes[field]
85
+ raise OptAR::Errors::TimeTypeExpectedError unless val.is_a?(Time)
86
+ attributes[field] = val.to_i
87
+ end
88
+ ensure
89
+ attributes.freeze
90
+ end
91
+
68
92
  def marshal_dump
69
93
  [@attributes, @klass_name]
70
94
  end
@@ -1,5 +1,5 @@
1
1
  require 'active_support/core_ext/kernel/singleton_class'
2
- require File.dirname(__FILE__) + '/../optimal_active_record'
2
+ require File.dirname(__FILE__) + '/../oar'
3
3
 
4
4
  module OptAR
5
5
  module OptimalAR
@@ -7,59 +7,90 @@ module OptAR
7
7
  module ClassMethods
8
8
  def build_opt_ar(name, options = {})
9
9
  validate_name(name)
10
+ validate_scope(options[:scope]) if options[:scope]
10
11
  faker_proc = lambda do |*args|
11
- options = options.respond_to?(:call) ? unscoped { options.call(*args) } : options
12
+ options = fetch_options(options, *args)
12
13
  scope = options[:scope]
13
- if scope
14
- valid_scope?(scope) ? safe_send(scope).opt_ar_objects(options) : throw_error(:undefined_scope, scope: scope)
15
- else
16
- safe_send(:build_default_scope).opt_ar_objects(options)
17
- end
14
+ fetch_optar_objects(scope, options)
18
15
  end
19
- singleton_class.safe_send(:redefine_method, name, &faker_proc)
16
+ singleton_class.send(:redefine_method, name, &faker_proc)
20
17
  end
21
18
 
22
19
  alias show_as build_opt_ar
23
20
 
24
21
  private
25
22
 
26
- def valid_scope?(scope_name)
27
- respond_to? scope_name
28
- end
23
+ def validate_scope(scope_name)
24
+ valid = valid_scope?(scope_name)
25
+ return if valid == true
26
+ throw_error(:undefined_scope, scope: scope_name)
27
+ end
29
28
 
30
- def validate_name(name)
31
- valid = valid_name?(name)
32
- throw_error(:invalid_name, name: name, type: valid) unless valid == true
33
- end
29
+ def valid_scope?(scope_name)
30
+ respond_to? scope_name
31
+ end
34
32
 
35
- def valid_name?(name)
36
- if !name.is_a?(Symbol)
37
- :symbol_expected
38
- elsif respond_to? name
39
- :duplicate_name
40
- else
41
- true
42
- end
43
- end
33
+ def validate_name(name)
34
+ valid = valid_name?(name)
35
+ return true if valid == true
36
+ throw_error(:invalid_name, name: name, type: valid)
37
+ end
44
38
 
45
- def throw_error(error_type, error_options)
46
- raise safe_send(error_type, error_options)
39
+ def valid_name?(name)
40
+ if !name.is_a?(Symbol)
41
+ :symbol_expected
42
+ elsif respond_to? name
43
+ :duplicate_name
44
+ else
45
+ true
47
46
  end
47
+ end
48
48
 
49
- def undefined_scope(options)
50
- Rails.logger.error(" :: show_as defined with Undefined Scope :: #{options[:scope]}")
51
- OptAR::Error::UndefinedScopeError.new(options)
49
+ def fetch_options(options, *args)
50
+ if options.respond_to?(:call)
51
+ unscoped { options.call(*args) }
52
+ else
53
+ options
52
54
  end
55
+ end
53
56
 
54
- def invalid_name(options)
55
- Rails.logger.error(" :: show_as defined with invalid name :: #{options[:name]} :: #{options[:type]}")
56
- OptAR::Error::DuplicateNameError.new(options)
57
+ def fetch_optar_objects(scope, options)
58
+ if scope
59
+ fetch_scoped_optar(scope, options)
60
+ else
61
+ fetch_default_scoped_optars(options)
57
62
  end
63
+ end
64
+
65
+ def fetch_scoped_optar(scope, options)
66
+ send(scope).opt_ar_objects(options)
67
+ end
68
+
69
+ def fetch_default_scoped_optars(options)
70
+ send(:build_default_scope).opt_ar_objects(options)
71
+ end
72
+
73
+ def throw_error(error_type, error_options)
74
+ raise send(error_type, error_options)
75
+ end
76
+
77
+ def undefined_scope(options)
78
+ msg = " :: show_as defined with Undefined Scope :: #{options[:scope]}"
79
+ OptAR::Logger.log(msg, :error)
80
+ OptAR::Errors::UndefinedScopeError.new(options)
81
+ end
82
+
83
+ def invalid_name(options)
84
+ error_options = "#{options[:name]} :: #{options[:type]}"
85
+ msg = " :: show_as defined with invalid name :: #{error_options}"
86
+ OptAR::Logger.log(msg, :error)
87
+ OptAR::Errors::DuplicateNameError.new(options)
88
+ end
58
89
  end
59
90
 
60
91
  module InstanceMethods
61
92
  def opt_ar_object(options = {})
62
- OptAR::OptimalActiveRecord.new(self, options)
93
+ OptAR::OAR.new(self, options)
63
94
  end
64
95
 
65
96
  alias opt_ar_objects opt_ar_object
@@ -1,3 +1,3 @@
1
1
  module OptAR
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.0.1'.freeze
3
3
  end
data/lib/opt_ar.rb CHANGED
@@ -1,23 +1,50 @@
1
1
  require 'opt_ar/version'
2
2
  require 'active_record'
3
3
  require_relative './opt_ar/optimal_ar/builder'
4
+ require_relative 'opt_ar/logger'
5
+ require_relative 'opt_ar/errors'
4
6
  # Dir["#{File.dirname(__FILE__)}/lib/opt_ar/**/*.rb"].each { |f| require f }
5
7
 
8
+ # Base module
6
9
  module OptAR
7
10
  end
8
11
 
9
12
  ActiveRecord::Base.send :include, OptAR::OptimalAR::Builder
10
13
 
11
14
  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
12
18
  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
13
25
  def opt_ar_objects(options = {})
14
26
  to_a.opt_ar_objects(options)
15
27
  end
16
28
  end
17
29
  end
18
30
 
31
+ # Overwriting Array to make opt_ar_objects method available
32
+ # for all arrays containing ActiveRecord::Base objects, returning
33
+ # an array of `OAR`s which we generate
19
34
  class Array
20
35
  def opt_ar_objects(options = {})
21
- map { |obj| obj.opt_ar_object(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
22
42
  end
23
43
  end
44
+
45
+ # TODO: Implement opt_ar_objects for hash
46
+ # class Hash
47
+ # def opt_ar_objects
48
+
49
+ # end
50
+ # end
data/opt_ar.gemspec CHANGED
@@ -9,15 +9,16 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['ragav0102']
10
10
  spec.email = ['ragavh123@gmail.com']
11
11
 
12
- spec.summary = 'Generates memory-optimal immutable ActiveRecord dupes'
12
+ spec.summary = 'Generates memory-friendly and immutable read-optimized'\
13
+ ' ActiveRecord dupes for caching and other purposes'
13
14
  spec.description = 'Generates memory-optimal immutable ActiveRecord dupes '\
14
15
  'that are easily serializable and behaves much like '\
15
- 'them. Define required attributes before-hand and use'\
16
- ' them just as you would on an AR, for better memory '\
17
- 'optimization.\n Ideally, suitable in place of caching '\
16
+ 'ARs. Define required attributes before-hand and use '\
17
+ 'them just as you would on an AR, for better memory '\
18
+ 'optimization. Ideally, suitable in place of caching '\
18
19
  'AR objects with cache stores like Memcached, where '\
19
20
  'serialization and de-serialization are memory-hungry'
20
- spec.homepage = "https://github.com/ragav0102/opt_ar"
21
+ spec.homepage = 'https://github.com/ragav0102/opt_ar'
21
22
  spec.license = 'MIT'
22
23
 
23
24
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
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.0
4
+ version: 1.0.1
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-01 00:00:00.000000000 Z
11
+ date: 2019-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -81,10 +81,10 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '10.0'
83
83
  description: Generates memory-optimal immutable ActiveRecord dupes that are easily
84
- serializable and behaves much like them. Define required attributes before-hand
85
- and use them just as you would on an AR, for better memory optimization.\n Ideally,
86
- suitable in place of caching AR objects with cache stores like Memcached, where
87
- serialization and de-serialization are memory-hungry
84
+ serializable and behaves much like ARs. Define required attributes before-hand and
85
+ use them just as you would on an AR, for better memory optimization. Ideally, suitable
86
+ in place of caching AR objects with cache stores like Memcached, where serialization
87
+ and de-serialization are memory-hungry
88
88
  email:
89
89
  - ragavh123@gmail.com
90
90
  executables: []
@@ -106,7 +106,8 @@ files:
106
106
  - lib/opt_ar/core_ext/array.rb
107
107
  - lib/opt_ar/errors.rb
108
108
  - lib/opt_ar/helpers/method_finder_helper.rb
109
- - lib/opt_ar/optimal_active_record.rb
109
+ - lib/opt_ar/logger.rb
110
+ - lib/opt_ar/oar.rb
110
111
  - lib/opt_ar/optimal_ar/builder.rb
111
112
  - lib/opt_ar/version.rb
112
113
  - opt_ar.gemspec
@@ -133,5 +134,6 @@ rubyforge_project:
133
134
  rubygems_version: 2.7.5
134
135
  signing_key:
135
136
  specification_version: 4
136
- summary: Generates memory-optimal immutable ActiveRecord dupes
137
+ summary: Generates memory-friendly and immutable read-optimized ActiveRecord dupes
138
+ for caching and other purposes
137
139
  test_files: []