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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05daa8fd91d2473f6cf34e98d2e5ac791f920894ba9320494acf5eda3f8ca0eb
4
- data.tar.gz: bfa798775b961b5d4d33c83fd78ca53fef8a7535903e0930c802793b1598f3a8
3
+ metadata.gz: 9bf739c60e437c0a17f2615dc5ab31001856006fdf2f6aee4cc2fd6ef6499219
4
+ data.tar.gz: d943d7dd7b81ad90ac7c8c5486afbbe81ccd42435704c7dbc07ebc761e1a9da6
5
5
  SHA512:
6
- metadata.gz: ea64ea8e8691073516396dd1ac53bd90b08c1a1fab8baeaf80d8c3dad2de6bc83f4f5128328246617052f2761755c5b26ef2c2a0f1e0a3317b547ef93dc21944
7
- data.tar.gz: 56d26b34be8033148c3452bd77e1177ab80eeb6247dc88ba4a45329e522f86bf76d719ba67ef997abe1471d0916add31fef4231107f93b20e9eddf84d3790ffc
6
+ metadata.gz: 8136acee5dd7dc95e9f7291223e99da527ad2a19d2b277bc5b6fba710c8140e816d93cb9b02e903b2b584ba9d11493f82a57e76b038568192c22324ba4bd94b3
7
+ data.tar.gz: 38ed562be1053fc966d12fc51496bd93117931407b3d11b1bd7d20203800a05c5f5750d6c2dbfb365fdd3d89a3a6972202312c1808f1129463825fc30de61951
@@ -0,0 +1,51 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ version: 2
4
+ jobs:
5
+ build:
6
+ docker:
7
+ - image: spittet/ruby-mysql
8
+
9
+ environment:
10
+ COVERALLS_REPO_TOKEN: coverallsRepoToken
11
+
12
+ working_directory: ~/repo
13
+
14
+ steps:
15
+ - checkout
16
+
17
+ - run:
18
+ name: upgrade bundler
19
+ command:
20
+ gem install bundler -v 1.16.1
21
+
22
+ - run:
23
+ name: install dependencies
24
+ command:
25
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
26
+
27
+ # Database setup
28
+ - run:
29
+ name: start mysql
30
+ command:
31
+ /etc/init.d/mysql start
32
+
33
+ - run: bundle exec rake db:create
34
+
35
+ # run tests!
36
+ - run:
37
+ name: run tests
38
+ command:
39
+ COVERALLS_REPO_TOKEN=$COVERALLS_REPO_TOKEN rake test
40
+
41
+ - run:
42
+ name: push result data to coveralls
43
+ command:
44
+ bundle exec rake coveralls:push
45
+
46
+ - store_test_results:
47
+ path: tmp/test-results
48
+
49
+ - store_artifacts:
50
+ path: tmp/test-results
51
+ destination: test-results
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service name: circleci
data/.gitignore CHANGED
@@ -8,3 +8,5 @@
8
8
  /tmp/
9
9
  .byebug_history
10
10
  /logs/
11
+ *.log
12
+ /coverage/
data/Gemfile CHANGED
@@ -9,8 +9,18 @@ gem 'activerecord', '>= 3.2'
9
9
 
10
10
  # gem 'byebug', '10.0.2'
11
11
 
12
- # gem 'minitest-reporters'
13
- # gem 'minitest-stub-const'
14
- # gem 'minitest-logger'
12
+ gem 'minitest-reporters'
13
+ gem 'minitest-stub-const'
15
14
 
16
15
  gem 'mysql2', '>= 0.3'
16
+
17
+ gem 'benchmark-ips'
18
+ gem 'benchmark-memory'
19
+
20
+ gem 'simplecov'
21
+ gem 'simplecov-console'
22
+ gem 'coveralls', require: false
23
+
24
+ # gem 'codeclimate-test-reporter'
25
+
26
+ gem 'minitest-ci'
data/Gemfile.lock CHANGED
@@ -1,35 +1,75 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opt_ar (1.0.1)
4
+ opt_ar (1.1.0)
5
5
  activerecord (>= 3.2)
6
6
  mysql2 (>= 0.3)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activemodel (4.2.6)
12
- activesupport (= 4.2.6)
13
- builder (~> 3.1)
14
- activerecord (4.2.6)
15
- activemodel (= 4.2.6)
16
- activesupport (= 4.2.6)
17
- arel (~> 6.0)
18
- activesupport (4.2.6)
19
- i18n (~> 0.7)
20
- json (~> 1.7, >= 1.7.7)
11
+ activemodel (5.2.3)
12
+ activesupport (= 5.2.3)
13
+ activerecord (5.2.3)
14
+ activemodel (= 5.2.3)
15
+ activesupport (= 5.2.3)
16
+ arel (>= 9.0)
17
+ activesupport (5.2.3)
18
+ concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ i18n (>= 0.7, < 2)
21
20
  minitest (~> 5.1)
22
- thread_safe (~> 0.3, >= 0.3.4)
23
21
  tzinfo (~> 1.1)
24
- arel (6.0.4)
22
+ ansi (1.5.0)
23
+ arel (9.0.0)
24
+ benchmark-ips (2.7.2)
25
+ benchmark-memory (0.1.2)
26
+ memory_profiler (~> 0.9)
25
27
  builder (3.2.3)
26
- i18n (0.8.1)
27
- json (1.8.6)
28
- minitest (5.10.1)
29
- mysql2 (0.4.9)
30
- rake (10.4.2)
28
+ concurrent-ruby (1.1.5)
29
+ coveralls (0.7.2)
30
+ multi_json (~> 1.3)
31
+ rest-client (= 1.6.7)
32
+ simplecov (>= 0.7)
33
+ term-ansicolor (= 1.2.2)
34
+ thor (= 0.18.1)
35
+ docile (1.1.5)
36
+ hirb (0.7.3)
37
+ i18n (1.5.1)
38
+ concurrent-ruby (~> 1.0)
39
+ memory_profiler (0.9.12)
40
+ mime-types (3.2.2)
41
+ mime-types-data (~> 3.2015)
42
+ mime-types-data (3.2019.0331)
43
+ minitest (5.11.3)
44
+ minitest-ci (3.4.0)
45
+ minitest (>= 5.0.6)
46
+ minitest-reporters (1.3.6)
47
+ ansi
48
+ builder
49
+ minitest (>= 5.0)
50
+ ruby-progressbar
51
+ minitest-stub-const (0.6)
52
+ multi_json (1.13.1)
53
+ mysql2 (0.5.2)
54
+ rake (10.5.0)
55
+ rest-client (1.6.7)
56
+ mime-types (>= 1.16)
57
+ ruby-progressbar (1.10.0)
58
+ simplecov (0.8.2)
59
+ docile (~> 1.1.0)
60
+ multi_json
61
+ simplecov-html (~> 0.8.0)
62
+ simplecov-console (0.4.2)
63
+ ansi
64
+ hirb
65
+ simplecov
66
+ simplecov-html (0.8.0)
67
+ term-ansicolor (1.2.2)
68
+ tins (~> 0.8)
69
+ thor (0.18.1)
31
70
  thread_safe (0.3.6)
32
- tzinfo (1.2.2)
71
+ tins (0.13.2)
72
+ tzinfo (1.2.5)
33
73
  thread_safe (~> 0.1)
34
74
 
35
75
  PLATFORMS
@@ -37,11 +77,19 @@ PLATFORMS
37
77
 
38
78
  DEPENDENCIES
39
79
  activerecord (>= 3.2)
80
+ benchmark-ips
81
+ benchmark-memory
40
82
  bundler (~> 1.16)
83
+ coveralls
41
84
  minitest (~> 5.0)
85
+ minitest-ci
86
+ minitest-reporters
87
+ minitest-stub-const
42
88
  mysql2 (>= 0.3)
43
89
  opt_ar!
44
90
  rake (~> 10.0)
91
+ simplecov
92
+ simplecov-console
45
93
 
46
94
  BUNDLED WITH
47
95
  1.16.1
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # OptAR
2
+ [![CircleCI](https://circleci.com/gh/ragav0102/opt_ar/tree/master.svg?style=svg)](https://circleci.com/gh/ragav0102/opt_ar/tree/master) [![Coverage Status](https://coveralls.io/repos/github/ragav0102/opt_ar/badge.svg?branch=master)](https://coveralls.io/github/ragav0102/opt_ar?branch=master) [![Gem Version](https://badge.fury.io/rb/opt_ar.svg)](https://badge.fury.io/rb/opt_ar) ![](https://ruby-gem-downloads-badge.herokuapp.com/opt_ar?type=total&style=plastic&color=brightgreen&total_label=)
2
3
 
3
- Generates memory-optimal immutable ActiveRecord dupes that are easily serializable and behaves much like them. Define required attributes before-hand and use them just as you would on an AR, for better memory optimization.
4
+ Generates memory-optimal immutable ActiveRecord dupes that are easily serializable and behaves much like ARs. Request attributes that will be read before-hand, and use them later just as you would on an AR, for better memory optimization.
4
5
 
5
- Ideally, suitable in place of caching AR objects with cache stores like Memcached, where serialization and de-serialization are memory-hungry
6
+ Ideally, suitable in place of caching AR objects with cache stores like Memcached, where serialization and de-serialization are memory-hungry. Optars can save upto *90%* of your memory(object allocations), while being upto *20x* faster, when fetching huge AR results.
6
7
 
7
8
  ## Installation
8
9
 
@@ -22,17 +23,146 @@ Or install it yourself as:
22
23
 
23
24
  ## Usage
24
25
 
25
- TODO: Write usage instructions here
26
+ Declare OptAR scopes for an ActiveRecord model like below
27
+
28
+ ```ruby
29
+ class Employee < ActiveRecord::Base
30
+ self.table_name = 'employee'.freeze
31
+ self.primary_key = 'emp_id'.freeze
32
+
33
+
34
+ scope :male_employees, lambda {
35
+ where(gender: 1)
36
+ }
37
+
38
+ swindle :male_names,
39
+ scope: :male_employees,
40
+ attrs: %i[emp_id first_name last_name]
41
+
42
+ swindle :emp_birth_dates
43
+ attrs: %i[birth_date]
44
+
45
+ swindle :all_emp_ids
46
+ end
47
+ ```
48
+
49
+ * **scope** - You can use a predefined scope/association.
50
+
51
+ If not given, gets the default scope for the model
52
+
53
+
54
+ * **attrs** - Request attributes that will be read later. Reading unrequested attributes is an **anti-pattern**.
55
+
56
+ If not given, stores only the primary key
57
+
58
+ ```ruby
59
+ > emp = Employee.male_names
60
+ # [#<OptAR::OAR:0x007fe5d1dc1dd8 @attributes={:emp_id=>10082, :first_name=>"Parviz", :last_name=>"Lortz"}, @klass_name="Employee">, #<OptAR::OAR:0x007fe5d1dc1810 @attributes={:emp_id=>10096, :first_name=>"Jayson", :last_name=>"Mandell"}, @klass_name="Employee">,..]
61
+
62
+ > emp.first.first_name
63
+ # => "Parviz"
64
+
65
+ > emp.first.birth_date
66
+ # WARNING :: Trying to access attr that was not requested :: birth_date
67
+ # Employee Load (1.1ms) SELECT `employee`.* FROM `employee` WHERE emp_id=10082 LIMIT 1
68
+ # => Mon, 09 Sep 1963
69
+ ```
70
+
71
+ ---
72
+
73
+ > Things to remember:
74
+ > - All OAR objects are immutable. Trying to write or change it will raise exceptions.
75
+ > - Again, OARs are __read-optimized__ AR dupes
76
+ > - Utilize it better for querying/caching, by defining attributes, only that are required
77
+
78
+
79
+ ---
80
+
81
+
82
+ Use them on _AR relations/ Array of ARs_ as well
83
+
84
+ ```ruby
85
+ > rel = Employee.all
86
+
87
+ > rel.optars
88
+ # => [#<OptAR::OAR:0x007fe5d11f2b88 @attributes={:emp_id=>10082}, @klass_name="Employee">, #<OptAR::OAR:0x007fe5d11f25c0 @attributes={:emp_id=>10096}, @klass_name="Employee">,..]
89
+
90
+ > arr = rel.to_a
91
+ # => [#<OptAR::OAR:0x007fe5d11885f8 @attributes={:emp_id=>10082}, @klass_name="Employee">, #<OptAR::OAR:0x007fe5d11939a8 @attributes={:emp_id=>10096}, @klass_name="Employee">,..]
92
+
93
+ > arr.optars(attrs: [:first_name])
94
+ # => [#<OptAR::OAR:0x007fe5d0953400 @attributes={:first_name=>"Parviz", :emp_id=>10082}, @klass_name="Employee">, #<OptAR::OAR:0x007fe5d09530b8 @attributes={:first_name=>"Jayson", :emp_id=>10096}, @klass_name="Employee">,..]
95
+
96
+ ```
97
+
98
+ ---
99
+
100
+
101
+ On an ActiveRecord,
102
+
103
+ ```ruby
104
+
105
+ > emp = Employee.first
106
+
107
+ > emp.optar(attrs: [:last_name, :birth_date])
108
+ # => #<OptAR::OAR:0x007fe5d096d558 @attributes={:last_name=>"Lortz", :birth_date=>Mon, 09 Sep 1963, :emp_id=>10082}, @klass_name="Employee">
109
+
110
+ ```
111
+ ---
112
+
113
+
114
+ _created_at_ and _updated_at_ fields are by default considered to be Time objects. Override them using the _const_,
115
+
116
+ ```ruby
117
+ DATE_TIME_ATTRIBUTES = %i[last_login_time]
118
+ ```
119
+ in your model.
120
+
121
+
122
+ * Why do I need to do this?
123
+
124
+ Beleive me, this will spare some memory for you.
125
+
126
+ ---
127
+
128
+
129
+ Restrict storing sensitive/PII info outside, by blacklisting them. Simply add this _const_ to your model
130
+
131
+ ```ruby
132
+ BLACKLISTED_ATTRIBUTES = %i[password persist_token]
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Benchmarks
138
+
139
+ Benchmark results for executing selects, marshal dumping and loads on ActiveRecord vs Optars are as follows:
140
+
141
+ | | Execution time | Memory saved |
142
+ |-|:----------------:|:--------------:|
143
+ | Select queries | 2-4x faster | 20-70%|
144
+ | Dumps | 2-6x faster | 30-80%|
145
+ | Loads | 5-20x faster | 70-90% |
146
+
147
+ _* Larger the data, greater the results_
148
+
149
+
150
+ Common caching solutions like Memcached by default, uses Marshalling to store and retrieve objects. Storing and retrieving AR objects, creates a lot of ruby objects in memory, which grows linearly with the number of ARs.
151
+
152
+ _Optars_ are here to save you, use them well.
153
+
154
+ Scripts used for benchmarking can be found inside benchmarks [folder](./benchmarks).
155
+
156
+
157
+
26
158
 
27
159
  ## Development
28
160
 
29
161
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
162
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
-
33
163
  ## Contributing
34
164
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/opt_ar. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
165
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ragav0102/opt_ar. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
166
 
37
167
  ## License
38
168
 
@@ -40,4 +170,8 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
170
 
41
171
  ## Code of Conduct
42
172
 
43
- Everyone interacting in the OptAR project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/opt_ar/blob/master/CODE_OF_CONDUCT.md).
173
+ Everyone interacting in the OptAR project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ragav0102/opt_ar/blob/master/CODE_OF_CONDUCT.md).
174
+
175
+
176
+
177
+ Available at https://rubygems.org/gems/opt_ar
data/Rakefile CHANGED
@@ -1,5 +1,9 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
+ require 'mysql2'
4
+ require 'active_record'
5
+ require 'active_record/connection_adapters/mysql2_adapter'
6
+ require 'coveralls/rake/task'
3
7
 
4
8
  Rake::TestTask.new(:test) do |t|
5
9
  t.libs << 'test'
@@ -7,4 +11,22 @@ Rake::TestTask.new(:test) do |t|
7
11
  t.test_files = FileList['test/*_test.rb']
8
12
  end
9
13
 
14
+ namespace :db do
15
+ task :create do
16
+ ActiveRecord::Base.establish_connection(
17
+ adapter: 'mysql2',
18
+ host: 'localhost',
19
+ username: 'root',
20
+ password: '',
21
+ pool: 5
22
+ )
23
+
24
+ conn = ActiveRecord::Base.connection
25
+ create_db = 'CREATE DATABASE optar_test'
26
+ conn.execute(create_db)
27
+ end
28
+ end
29
+
10
30
  task default: :test
31
+
32
+ Coveralls::RakeTask.new
@@ -0,0 +1,131 @@
1
+ require 'benchmark/memory'
2
+
3
+ def bm_time
4
+ old_logger = ActiveRecord::Base.logger
5
+ ActiveRecord::Base.logger = nil
6
+ Benchmark.bm(12) do |x|
7
+ x.report('AR') do
8
+ emp = Employee.all.to_a
9
+ emp.last.emp_id
10
+ end
11
+
12
+ x.report('AR select') do
13
+ emp = Employee.all.select(%i[emp_id first_name last_name created_at]).to_a
14
+ emp.last.emp_id
15
+ end
16
+
17
+ x.report('OptAR') do
18
+ emp = Employee.all.optars
19
+ emp.last.emp_id
20
+ end
21
+
22
+ x.report('OptAR req') do
23
+ emp = Employee.all.optars(
24
+ req_attribute: %i[emp_id first_name last_name created_at]
25
+ )
26
+ emp.last.emp_id
27
+ end
28
+ end
29
+ ActiveRecord::Base.logger = old_logger
30
+
31
+ nil
32
+ end
33
+
34
+ def bm_mem
35
+ old_logger = ActiveRecord::Base.logger
36
+ ActiveRecord::Base.logger = nil
37
+
38
+ Benchmark.memory do |x|
39
+ x.compare!
40
+ x.report('AR') do
41
+ emp = Employee.all.to_a
42
+ emp.last.emp_id
43
+ end
44
+
45
+ x.report('AR select') do
46
+ emp = Employee.all.select(%i[emp_id first_name last_name created_at]).to_a
47
+ emp.last.emp_id
48
+ end
49
+
50
+ x.report('OptAR') do
51
+ emp = Employee.all.optars
52
+ emp.last.emp_id
53
+ end
54
+
55
+ x.report('OptAR req') do
56
+ emp = Employee.all.optars(
57
+ req_attribute: %i[emp_id first_name last_name created_at]
58
+ )
59
+ emp.last.emp_id
60
+ end
61
+ end
62
+ ActiveRecord::Base.logger = old_logger
63
+
64
+ nil
65
+ end
66
+
67
+ bm_time
68
+
69
+ bm_mem
70
+
71
+ # 1042 objects:
72
+ # AR 2.173M memsize ( 1.208k retained)
73
+ # 22.114k objects ( 1.000 retained)
74
+ # 50.000 strings ( 0.000 retained)
75
+ # AR select 1.326M memsize ( 1.208k retained)
76
+ # 15.938k objects ( 1.000 retained)
77
+ # 50.000 strings ( 0.000 retained)
78
+ # OptAR 1.054M memsize ( 1.208k retained)
79
+ # 14.840k objects ( 1.000 retained)
80
+ # 19.000 strings ( 0.000 retained)
81
+ # OptAR req 1.054M memsize ( 1.208k retained)
82
+ # 14.841k objects ( 1.000 retained)
83
+ # 19.000 strings ( 0.000 retained)
84
+
85
+ # Comparison:
86
+ # OptAR: 1053649 allocated
87
+ # OptAR req: 1053913 allocated - 1.00x more
88
+ # AR select: 1326421 allocated - 1.26x more
89
+ # AR: 2172969 allocated - 2.06x more
90
+
91
+ # user system total real
92
+ # AR 0.020000 0.000000 0.020000 ( 0.028664)
93
+ # AR select 0.020000 0.000000 0.020000 ( 0.022151)
94
+ # OptAR 0.010000 0.000000 0.010000 ( 0.010412)
95
+ # OptAR req 0.010000 0.000000 0.010000 ( 0.010862)
96
+
97
+ # AR 0.020000 0.000000 0.020000 ( 0.029021)
98
+ # AR select 0.030000 0.000000 0.030000 ( 0.028373)
99
+ # OptAR 0.010000 0.000000 0.010000 ( 0.012695)
100
+ # OptAR req 0.010000 0.000000 0.010000 ( 0.011037)
101
+
102
+ # 11042 objects:
103
+ # AR 22.809M memsize ( 1.208k retained)
104
+ # 232.114k objects ( 1.000 retained)
105
+ # 50.000 strings ( 0.000 retained)
106
+ # AR select 13.802M memsize ( 1.208k retained)
107
+ # 165.938k objects ( 1.000 retained)
108
+ # 50.000 strings ( 0.000 retained)
109
+ # OptAR 10.972M memsize ( 1.208k retained)
110
+ # 154.840k objects ( 1.000 retained)
111
+ # 19.000 strings ( 0.000 retained)
112
+ # OptAR req 10.972M memsize ( 1.208k retained)
113
+ # 154.841k objects ( 1.000 retained)
114
+ # 19.000 strings ( 0.000 retained)
115
+
116
+ # Comparison:
117
+ # OptAR: 10971521 allocated
118
+ # OptAR req: 10971785 allocated - 1.00x more
119
+ # AR select: 13802165 allocated - 1.26x more
120
+ # AR: 22808713 allocated - 2.08x more
121
+
122
+ # user system total real
123
+ # AR 0.210000 0.010000 0.220000 ( 0.235152)
124
+ # AR select 0.200000 0.010000 0.210000 ( 0.217465)
125
+ # OptAR 0.090000 0.000000 0.090000 ( 0.105298)
126
+ # OptAR req 0.070000 0.010000 0.080000 ( 0.088627)
127
+
128
+ # AR 0.210000 0.010000 0.220000 ( 0.235152)
129
+ # AR select 0.200000 0.010000 0.210000 ( 0.217465)
130
+ # OptAR 0.090000 0.000000 0.090000 ( 0.105298)
131
+ # OptAR req 0.070000 0.010000 0.080000 ( 0.088627)