quickery 0.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 +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.travis.yml +9 -0
- data/Appraisals +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +163 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +209 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +9 -0
- data/developer_guide.md +26 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_3.gemfile +7 -0
- data/gemfiles/rails_4.gemfile +11 -0
- data/gemfiles/rails_5.gemfile +11 -0
- data/lib/quickery/active_record_extensions/dsl.rb +44 -0
- data/lib/quickery/association_builder.rb +108 -0
- data/lib/quickery/callbacks_builder.rb +107 -0
- data/lib/quickery/quickery_builder.rb +41 -0
- data/lib/quickery/version.rb +3 -0
- data/lib/quickery.rb +6 -0
- data/quickery.gemspec +38 -0
- metadata +257 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3bb77d2c32c2ce80c651fd671701fd561c4ba37d8920827804bf5d092cd5085e
|
|
4
|
+
data.tar.gz: 84388c8df61f76837adfd630b9c8a0421d818d5098b4d3966a547ee8408efd04
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3f8f3efade73d92df662b8f43aaaab39af3b22390c83f3f2ff82ce292cdae737e451dbd83c1b5981d7272c5effbbb294288d823a6ea07002be3deb12277ff738
|
|
7
|
+
data.tar.gz: 1229a87bf58a64bcc2f2fe1e014bc4c96b28f07ebc537b0e73164a30c4775e515db4730c927957c133263a58097471a429bbc48d5399faed2bf546c689a4e2fa
|
data/.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/.bundle/
|
|
2
|
+
/.yardoc
|
|
3
|
+
/_yardoc/
|
|
4
|
+
/coverage/
|
|
5
|
+
/doc/
|
|
6
|
+
/pkg/
|
|
7
|
+
/spec/reports/
|
|
8
|
+
/tmp/
|
|
9
|
+
|
|
10
|
+
/spec/internal/log/*
|
|
11
|
+
/spec/internal/tmp/*
|
|
12
|
+
*.sqlite
|
|
13
|
+
*.sqlite-journal
|
|
14
|
+
|
|
15
|
+
# rspec failure tracking
|
|
16
|
+
.rspec_status
|
|
17
|
+
|
|
18
|
+
*.gem
|
|
19
|
+
.byebug_history
|
|
20
|
+
|
|
21
|
+
Gemfile.lock
|
|
22
|
+
gemfiles/*.lock
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Appraisals
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
quickery (0.1.0)
|
|
5
|
+
activerecord
|
|
6
|
+
activesupport
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
actionpack (5.2.0)
|
|
12
|
+
actionview (= 5.2.0)
|
|
13
|
+
activesupport (= 5.2.0)
|
|
14
|
+
rack (~> 2.0)
|
|
15
|
+
rack-test (>= 0.6.3)
|
|
16
|
+
rails-dom-testing (~> 2.0)
|
|
17
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
18
|
+
actionview (5.2.0)
|
|
19
|
+
activesupport (= 5.2.0)
|
|
20
|
+
builder (~> 3.1)
|
|
21
|
+
erubi (~> 1.4)
|
|
22
|
+
rails-dom-testing (~> 2.0)
|
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
|
24
|
+
activemodel (5.2.0)
|
|
25
|
+
activesupport (= 5.2.0)
|
|
26
|
+
activerecord (5.2.0)
|
|
27
|
+
activemodel (= 5.2.0)
|
|
28
|
+
activesupport (= 5.2.0)
|
|
29
|
+
arel (>= 9.0)
|
|
30
|
+
activesupport (5.2.0)
|
|
31
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
32
|
+
i18n (>= 0.7, < 2)
|
|
33
|
+
minitest (~> 5.1)
|
|
34
|
+
tzinfo (~> 1.1)
|
|
35
|
+
appraisal (2.2.0)
|
|
36
|
+
bundler
|
|
37
|
+
rake
|
|
38
|
+
thor (>= 0.14.0)
|
|
39
|
+
arel (9.0.0)
|
|
40
|
+
builder (3.2.3)
|
|
41
|
+
byebug (9.1.0)
|
|
42
|
+
coderay (1.1.2)
|
|
43
|
+
combustion (0.9.1)
|
|
44
|
+
activesupport (>= 3.0.0)
|
|
45
|
+
railties (>= 3.0.0)
|
|
46
|
+
thor (>= 0.14.6)
|
|
47
|
+
concurrent-ruby (1.0.5)
|
|
48
|
+
crass (1.0.4)
|
|
49
|
+
database_cleaner (1.7.0)
|
|
50
|
+
diff-lcs (1.3)
|
|
51
|
+
erubi (1.7.1)
|
|
52
|
+
factory_bot (4.10.0)
|
|
53
|
+
activesupport (>= 3.0.0)
|
|
54
|
+
factory_bot_rails (4.10.0)
|
|
55
|
+
factory_bot (~> 4.10.0)
|
|
56
|
+
railties (>= 3.0.0)
|
|
57
|
+
faker (1.9.1)
|
|
58
|
+
i18n (>= 0.7)
|
|
59
|
+
ffi (1.9.25)
|
|
60
|
+
formatador (0.2.5)
|
|
61
|
+
guard (2.14.2)
|
|
62
|
+
formatador (>= 0.2.4)
|
|
63
|
+
listen (>= 2.7, < 4.0)
|
|
64
|
+
lumberjack (>= 1.0.12, < 2.0)
|
|
65
|
+
nenv (~> 0.1)
|
|
66
|
+
notiffany (~> 0.0)
|
|
67
|
+
pry (>= 0.9.12)
|
|
68
|
+
shellany (~> 0.0)
|
|
69
|
+
thor (>= 0.18.1)
|
|
70
|
+
guard-compat (1.2.1)
|
|
71
|
+
guard-rspec (4.7.3)
|
|
72
|
+
guard (~> 2.1)
|
|
73
|
+
guard-compat (~> 1.1)
|
|
74
|
+
rspec (>= 2.99.0, < 4.0)
|
|
75
|
+
i18n (1.0.1)
|
|
76
|
+
concurrent-ruby (~> 1.0)
|
|
77
|
+
listen (3.1.5)
|
|
78
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
79
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
|
80
|
+
ruby_dep (~> 1.2)
|
|
81
|
+
loofah (2.2.2)
|
|
82
|
+
crass (~> 1.0.2)
|
|
83
|
+
nokogiri (>= 1.5.9)
|
|
84
|
+
lumberjack (1.0.13)
|
|
85
|
+
method_source (0.9.0)
|
|
86
|
+
mini_portile2 (2.3.0)
|
|
87
|
+
minitest (5.11.3)
|
|
88
|
+
nenv (0.3.0)
|
|
89
|
+
nokogiri (1.8.4)
|
|
90
|
+
mini_portile2 (~> 2.3.0)
|
|
91
|
+
notiffany (0.1.1)
|
|
92
|
+
nenv (~> 0.1)
|
|
93
|
+
shellany (~> 0.0)
|
|
94
|
+
pry (0.11.3)
|
|
95
|
+
coderay (~> 1.1.0)
|
|
96
|
+
method_source (~> 0.9.0)
|
|
97
|
+
rack (2.0.5)
|
|
98
|
+
rack-test (1.1.0)
|
|
99
|
+
rack (>= 1.0, < 3)
|
|
100
|
+
rails-dom-testing (2.0.3)
|
|
101
|
+
activesupport (>= 4.2.0)
|
|
102
|
+
nokogiri (>= 1.6)
|
|
103
|
+
rails-html-sanitizer (1.0.4)
|
|
104
|
+
loofah (~> 2.2, >= 2.2.2)
|
|
105
|
+
railties (5.2.0)
|
|
106
|
+
actionpack (= 5.2.0)
|
|
107
|
+
activesupport (= 5.2.0)
|
|
108
|
+
method_source
|
|
109
|
+
rake (>= 0.8.7)
|
|
110
|
+
thor (>= 0.18.1, < 2.0)
|
|
111
|
+
rake (10.5.0)
|
|
112
|
+
rb-fsevent (0.10.3)
|
|
113
|
+
rb-inotify (0.9.10)
|
|
114
|
+
ffi (>= 0.5.0, < 2)
|
|
115
|
+
rspec (3.7.0)
|
|
116
|
+
rspec-core (~> 3.7.0)
|
|
117
|
+
rspec-expectations (~> 3.7.0)
|
|
118
|
+
rspec-mocks (~> 3.7.0)
|
|
119
|
+
rspec-core (3.7.1)
|
|
120
|
+
rspec-support (~> 3.7.0)
|
|
121
|
+
rspec-expectations (3.7.0)
|
|
122
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
123
|
+
rspec-support (~> 3.7.0)
|
|
124
|
+
rspec-mocks (3.7.0)
|
|
125
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
126
|
+
rspec-support (~> 3.7.0)
|
|
127
|
+
rspec-rails (3.7.2)
|
|
128
|
+
actionpack (>= 3.0)
|
|
129
|
+
activesupport (>= 3.0)
|
|
130
|
+
railties (>= 3.0)
|
|
131
|
+
rspec-core (~> 3.7.0)
|
|
132
|
+
rspec-expectations (~> 3.7.0)
|
|
133
|
+
rspec-mocks (~> 3.7.0)
|
|
134
|
+
rspec-support (~> 3.7.0)
|
|
135
|
+
rspec-support (3.7.1)
|
|
136
|
+
ruby_dep (1.5.0)
|
|
137
|
+
shellany (0.0.1)
|
|
138
|
+
sqlite3 (1.3.13)
|
|
139
|
+
thor (0.20.0)
|
|
140
|
+
thread_safe (0.3.6)
|
|
141
|
+
tzinfo (1.2.5)
|
|
142
|
+
thread_safe (~> 0.1)
|
|
143
|
+
|
|
144
|
+
PLATFORMS
|
|
145
|
+
ruby
|
|
146
|
+
|
|
147
|
+
DEPENDENCIES
|
|
148
|
+
appraisal (~> 2.2.0)
|
|
149
|
+
bundler (~> 1.16)
|
|
150
|
+
byebug (~> 9.0)
|
|
151
|
+
combustion (~> 0.9.1)
|
|
152
|
+
database_cleaner (~> 1.7.0)
|
|
153
|
+
factory_bot_rails
|
|
154
|
+
faker (~> 1.9.1)
|
|
155
|
+
guard-rspec (~> 4.7.3)
|
|
156
|
+
quickery!
|
|
157
|
+
rake (~> 10.0)
|
|
158
|
+
rspec (~> 3.7.0)
|
|
159
|
+
rspec-rails (~> 3.7.2)
|
|
160
|
+
sqlite3 (~> 1.3.13)
|
|
161
|
+
|
|
162
|
+
BUNDLED WITH
|
|
163
|
+
1.16.1
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Jules Roman Polidario
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Quickery
|
|
2
|
+
|
|
3
|
+
## About
|
|
4
|
+
|
|
5
|
+
* Implements Law of Demeter by mapping associated record attributes as own attributes (one-way read-only)
|
|
6
|
+
* Consequently, speeds up SQL queries by removing joins queries between intermediary models, at the cost of slower writes.
|
|
7
|
+
* This is an anti-normalization pattern in favour of actual data-redundancy and faster queries. Use this only as necessary.
|
|
8
|
+
|
|
9
|
+
## Dependencies
|
|
10
|
+
|
|
11
|
+
* **Rails 4 or 5**
|
|
12
|
+
* **(Rails 3 still untested)**
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
1. Add the following to your `Gemfile`:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'quickery', '~> 0.1'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Run:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage Example 1
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# app/models/employee.rb
|
|
31
|
+
class Employee < ApplicationRecord
|
|
32
|
+
# say we have the following attributes:
|
|
33
|
+
# branch_id:integer
|
|
34
|
+
# branch_company_name:string
|
|
35
|
+
belongs_to :branch
|
|
36
|
+
|
|
37
|
+
quickery do
|
|
38
|
+
# TL;RD: the following line means:
|
|
39
|
+
# make sure that this record's `branch_company_name` attribute will always have
|
|
40
|
+
# the same value as branch.company.name and updates the value accordingly if it changes
|
|
41
|
+
branch.company.name == :branch_company_name
|
|
42
|
+
|
|
43
|
+
# feel free to rename :branch_company_name as you wish; it's just like any other attribute anyway
|
|
44
|
+
# the == is a custom overloaded operator; it does not mean "is equal" but means "should equal to"
|
|
45
|
+
# branch.company.name is a fluid expression that defines the attribute dependency
|
|
46
|
+
# `branch` and `company` does not mean `branch` and `company` record
|
|
47
|
+
|
|
48
|
+
# you may add more inside this quickery-block: i.e:
|
|
49
|
+
# branch.name == :branch_name
|
|
50
|
+
# branch.id == :branch_id
|
|
51
|
+
# branch.company.country.name == :branch_company_country_name
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# app/models/branch.rb
|
|
56
|
+
class Branch < ApplicationRecord
|
|
57
|
+
# say we have the following attributes:
|
|
58
|
+
# company_id:integer
|
|
59
|
+
belongs_to :company
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# app/models/company.rb
|
|
63
|
+
class Company < ApplicationRecord
|
|
64
|
+
# say we have the following attributes:
|
|
65
|
+
# name:string
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# bash
|
|
71
|
+
rails generate migration add_branch_company_name_to_employees branch_company_name:string
|
|
72
|
+
bundle exec rake db:migrate
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# rails console
|
|
77
|
+
company = Company.create!(name: 'Jollibee')
|
|
78
|
+
branch = Branch.create!(company: company)
|
|
79
|
+
employee = Employee.create!(branch: branch)
|
|
80
|
+
|
|
81
|
+
puts employee.branch_company_name
|
|
82
|
+
# => 'Jollibee'
|
|
83
|
+
|
|
84
|
+
# As you can see the `branch_company_name` attribute above has the same value as the associated record's attribute
|
|
85
|
+
# Now, let's try updating company, and you will see below that `branch_company_name` automatically gets updated as well
|
|
86
|
+
|
|
87
|
+
company.update!(name: 'Mang Inasal')
|
|
88
|
+
|
|
89
|
+
puts employee.branch_company_name
|
|
90
|
+
# => 'Jollibee'
|
|
91
|
+
|
|
92
|
+
# You need to reload the object, if you expect that it's been changed:
|
|
93
|
+
employee.reload
|
|
94
|
+
|
|
95
|
+
puts employee.branch_company_name
|
|
96
|
+
# => 'Mang Inasal'
|
|
97
|
+
|
|
98
|
+
# Now, let's try updating the intermediary association, and you will see below that `branch_company_name` would be updated accordingly
|
|
99
|
+
other_company = Company.create!(name: 'McDonalds')
|
|
100
|
+
branch.update!(company: other_company)
|
|
101
|
+
|
|
102
|
+
employee.reload
|
|
103
|
+
|
|
104
|
+
puts employee.branch_company_name
|
|
105
|
+
# => 'McDonalds'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Usage Example 2
|
|
109
|
+
|
|
110
|
+
* let `Branch` and `Company` model be the same as the Usage Example 1 above
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# app/models/employee.rb
|
|
114
|
+
class Employee < ApplicationRecord
|
|
115
|
+
belongs_to :branch
|
|
116
|
+
belongs_to :country, foreign_key: :branch_company_id
|
|
117
|
+
|
|
118
|
+
quickery do
|
|
119
|
+
branch.company.id == :branch_company_id
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# bash
|
|
126
|
+
rails generate migration add_branch_company_id_to_employees branch_company_id:bigint
|
|
127
|
+
bundle exec rake db:migrate
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# rails console
|
|
132
|
+
company = Company.create!(name: 'Jollibee')
|
|
133
|
+
branch = Branch.create!(company: company)
|
|
134
|
+
employee = Employee.create!(branch: branch)
|
|
135
|
+
|
|
136
|
+
puts employee.branch_company_id
|
|
137
|
+
# => 1
|
|
138
|
+
|
|
139
|
+
puts Employee.where(company: company)
|
|
140
|
+
# => [#<Employee id: 1>]
|
|
141
|
+
|
|
142
|
+
# as you may notice, the query above is a lot simpler and faster instead of doing it normally like below (if not using Quickery)
|
|
143
|
+
# you may however still using belongs_to `:through` to achieve the simplified query like above, but it's still a lot slower because of JOINS
|
|
144
|
+
puts Employee.joins(branch: :company).where(companies: { id: company.id })
|
|
145
|
+
# => [#<Employee id: 1>]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## DSL
|
|
149
|
+
|
|
150
|
+
### For any subclass of `ActiveRecord::Base`:
|
|
151
|
+
|
|
152
|
+
#### Class Methods:
|
|
153
|
+
|
|
154
|
+
##### `quickery(&block)`
|
|
155
|
+
* returns a `Quickery::AssociationBuilder` object
|
|
156
|
+
* block is executed in the context of the `Quickery::AssociationBuilder` object,
|
|
157
|
+
which means that you cannot access the model instance inside the block, as you are not supposed to.
|
|
158
|
+
* inside the block you may define "quickery-defined attribute mappings";
|
|
159
|
+
each mapping will create a `Quickery::QuickeryBuilder` object. i.e:
|
|
160
|
+
* `branch.company.country.category.name == :branch_company_country_category_name`
|
|
161
|
+
* You are required to specify `belongs_to :branch` association in this model.
|
|
162
|
+
* Similarly, you are required to specify `belongs_to :company` inside `Branch` model, `belongs_to :country` inside `Company` model; etc...
|
|
163
|
+
* each `Quickery::AssociationBuilder` defines a set of "hidden" `before_save`, `before_update`, `before_destroy`, and `before_create` callbacks across all models specified in the quickery-defined attribute association chain.
|
|
164
|
+
* quickery-defined attributes such as say `:branch_company_country_category_name` are updated by Quickery automatically whenever any of it's dependent records across models have been changed. Note that updates in this way do not trigger model callbacks, as I wanted to isolate logic and scope of Quickery by not triggering model callbacks that you already have.
|
|
165
|
+
* quickery-defined attributes such as say `:branch_company_country_category_name` are READ-only! Do not update these attributes manually. You can, but it will not automatically update the other end, and thus will break data integrity. If you want to re-update these attributes to match the other end, see `recreate_quickery_cache!` below.
|
|
166
|
+
|
|
167
|
+
##### `quickery_builders`
|
|
168
|
+
* returns an `Array` of `Quickery::QuickeryBuilder` objects that have already been defined
|
|
169
|
+
* for more info, see `quickery(&block)` above
|
|
170
|
+
* you normally do not need to use this method
|
|
171
|
+
|
|
172
|
+
#### Instance Methods:
|
|
173
|
+
|
|
174
|
+
##### `recreate_quickery_cache!`
|
|
175
|
+
* force-updates the quickery-defined attributes
|
|
176
|
+
* useful if you already have records, and you want these old records to be updated immediately
|
|
177
|
+
* i.e. you can do so something like the following:
|
|
178
|
+
```ruby
|
|
179
|
+
Employee.each do |employee|
|
|
180
|
+
employee.recreate_quickery_cache!
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
##### `determine_quickery_value(depender_column_name)`
|
|
185
|
+
* returns the current "actual" supposed value of the "original" dependee column
|
|
186
|
+
* useful for debugging to check if the quickery-defined attributes do not have correct mapped values
|
|
187
|
+
* i.e. you can do something like the following:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
employee = Employee.first
|
|
191
|
+
puts employee.determine_quickery_value(:branch_company_country_name)
|
|
192
|
+
# => 'Ireland'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## TODOs
|
|
196
|
+
* Possibly support two-way mapping of attributes? So that you can do, say... `employee.update!(branch_company_name: 'somenewcompanyname')`
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
199
|
+
* pull requests and forks are very much welcomed! :) Let me know if you find any bug! Thanks
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
* MIT
|
|
203
|
+
|
|
204
|
+
## Developer Guide
|
|
205
|
+
* see [developer_guide.md](developer_guide.md)
|
|
206
|
+
|
|
207
|
+
## Changelog
|
|
208
|
+
* 0.1.0
|
|
209
|
+
* initial beta release
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "quickery"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config.ru
ADDED
data/developer_guide.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## Development
|
|
2
|
+
|
|
3
|
+
### Publishing as Ruby Gem
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# [increment gem VERSION]
|
|
7
|
+
gem build quickery
|
|
8
|
+
gem push quickery-X.X.X.gem
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Test
|
|
12
|
+
|
|
13
|
+
* Test suite makes use of the following gems worth mentioning (in addition to some others):
|
|
14
|
+
* [rspec-rails](https://github.com/rspec/rspec-rails)
|
|
15
|
+
* [combustion](https://github.com/pat/combustion)
|
|
16
|
+
* [appraisal](https://github.com/thoughtbot/appraisal)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# to auto-test specs whenever a spec file has been modified:
|
|
20
|
+
bundle exec guard
|
|
21
|
+
|
|
22
|
+
# to manually run specs for a particular rails version (for more info: see Appraisals file):
|
|
23
|
+
bundle exec appraisal rails-5 rspec
|
|
24
|
+
# or
|
|
25
|
+
bundle exec appraisal rails-4 rspec
|
|
26
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'byebug'
|
|
2
|
+
require 'active_support'
|
|
3
|
+
|
|
4
|
+
module Quickery
|
|
5
|
+
module ActiveRecordExtensions
|
|
6
|
+
module DSL
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.extend ClassMethods
|
|
9
|
+
base.include InstanceMethods
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def quickery(&block)
|
|
14
|
+
association_builder = AssociationBuilder.new(model: self)
|
|
15
|
+
association_builder.instance_exec(&block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module InstanceMethods
|
|
20
|
+
def recreate_quickery_cache!
|
|
21
|
+
self.class.quickery_builders.each do |depender_column_name, quickery_builder|
|
|
22
|
+
new_value = determine_quickery_value(depender_column_name)
|
|
23
|
+
update_columns(depender_column_name => new_value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def determine_quickery_value(depender_column_name)
|
|
30
|
+
quickery_builder = self.class.quickery_builders[depender_column_name]
|
|
31
|
+
|
|
32
|
+
raise ArgumentError, "No defined quickery builder for #{depender_column_name}. Defined values are #{self.class.quickery_builders.keys}" unless quickery_builder
|
|
33
|
+
|
|
34
|
+
dependee_record = quickery_builder.first_association_builder._quickery_dependee_record(self)
|
|
35
|
+
dependee_record.send(quickery_builder.dependee_column_name)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ActiveSupport.on_load(:active_record) do
|
|
43
|
+
include Quickery::ActiveRecordExtensions::DSL
|
|
44
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Quickery
|
|
2
|
+
class AssociationBuilder
|
|
3
|
+
attr_reader :model
|
|
4
|
+
attr_reader :parent_builder
|
|
5
|
+
attr_reader :child_builder
|
|
6
|
+
attr_reader :inverse_association_name
|
|
7
|
+
attr_reader :belongs_to
|
|
8
|
+
|
|
9
|
+
def initialize(model:, parent_builder: nil, inverse_association_name: nil)
|
|
10
|
+
@model = model
|
|
11
|
+
@parent_builder = parent_builder
|
|
12
|
+
@inverse_association_name = inverse_association_name
|
|
13
|
+
@reflections = model.reflections
|
|
14
|
+
@belongs_to_association_names = @reflections.map{ |key, value| value.macro == :belongs_to ? key : nil }.compact
|
|
15
|
+
@column_names = model.column_names
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# we need to prepend _quickery to all methods, to make sure no conflicts with association names that are dynamically invoked through `method_missing` below
|
|
19
|
+
|
|
20
|
+
def _quickery_get_child_builders(include_self: false, builders: [])
|
|
21
|
+
builders << self if include_self
|
|
22
|
+
|
|
23
|
+
if @child_builder.nil?
|
|
24
|
+
builders
|
|
25
|
+
else
|
|
26
|
+
builders << @child_builder
|
|
27
|
+
return @child_builder._quickery_get_child_builders(builders: builders)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _quickery_get_parent_builders(include_self: false, builders: [])
|
|
32
|
+
builders << self if include_self
|
|
33
|
+
|
|
34
|
+
if @parent_builder.nil?
|
|
35
|
+
builders
|
|
36
|
+
else
|
|
37
|
+
builders << @parent_builder
|
|
38
|
+
@parent_builder._quickery_get_parent_builders(builders: builders)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def _quickery_get_joins_arg(current_joins_arg = nil)
|
|
43
|
+
if @parent_builder.nil?
|
|
44
|
+
current_joins_arg
|
|
45
|
+
else
|
|
46
|
+
if current_joins_arg.nil?
|
|
47
|
+
@parent_builder._quickery_get_joins_arg(@inverse_association_name.to_sym)
|
|
48
|
+
else
|
|
49
|
+
@parent_builder._quickery_get_joins_arg({ @inverse_association_name.to_sym => current_joins_arg })
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def _quickery_dependent_records(record_to_be_saved)
|
|
55
|
+
primary_key_value = record_to_be_saved.send(record_to_be_saved.class.primary_key)
|
|
56
|
+
most_parent_model = _quickery_get_parent_builders.last.model
|
|
57
|
+
|
|
58
|
+
records = most_parent_model.all
|
|
59
|
+
|
|
60
|
+
unless (joins_arg = _quickery_get_joins_arg).nil?
|
|
61
|
+
records = records.joins(joins_arg)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
records = records.where(
|
|
65
|
+
model.table_name => {
|
|
66
|
+
model.primary_key => primary_key_value
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def _quickery_dependee_record(record_to_be_saved)
|
|
72
|
+
raise ArgumentError, 'argument should be an instance of @model' unless record_to_be_saved.is_a? model
|
|
73
|
+
|
|
74
|
+
_quickery_get_child_builders(include_self: true).inject(record_to_be_saved) do |record, association_builder|
|
|
75
|
+
if association_builder.belongs_to
|
|
76
|
+
record.send(association_builder.belongs_to.name)
|
|
77
|
+
else
|
|
78
|
+
record
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def method_missing(method_name, *args, &block)
|
|
86
|
+
method_name_str = method_name.to_s
|
|
87
|
+
if @belongs_to_association_names.include? method_name_str
|
|
88
|
+
@belongs_to = @reflections[method_name_str]
|
|
89
|
+
@child_builder = AssociationBuilder.new(model: belongs_to.class_name.constantize, parent_builder: self, inverse_association_name: method_name_str)
|
|
90
|
+
elsif @column_names.include? method_name_str
|
|
91
|
+
QuickeryBuilder.new(dependee_column_name: method_name_str, last_association_builder: self)
|
|
92
|
+
else
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def respond_to_missing(method_name, include_private = false)
|
|
98
|
+
method_name_str = method_name.to_s
|
|
99
|
+
if @belongs_to_association_names.include? method_name_str
|
|
100
|
+
true
|
|
101
|
+
elsif @column_names.include? method_name_str
|
|
102
|
+
true
|
|
103
|
+
else
|
|
104
|
+
super
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Quickery
|
|
2
|
+
class CallbacksBuilder
|
|
3
|
+
attr_reader :quickery_builder
|
|
4
|
+
|
|
5
|
+
def initialize(quickery_builder:, should_add_callbacks: true)
|
|
6
|
+
@quickery_builder = quickery_builder
|
|
7
|
+
add_callbacks if should_add_callbacks
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def add_callbacks
|
|
13
|
+
add_callback_to_depender_model
|
|
14
|
+
add_callback_to_dependee_model
|
|
15
|
+
add_callback_to_each_intermediate_model
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# add callback to immediately sync value after a record has been created / updated
|
|
19
|
+
def add_callback_to_depender_model
|
|
20
|
+
first_association_builder = @quickery_builder.first_association_builder
|
|
21
|
+
depender_column_name = @quickery_builder.depender_column_name
|
|
22
|
+
dependee_column_name = @quickery_builder.dependee_column_name
|
|
23
|
+
|
|
24
|
+
first_association_builder.model.class_exec do
|
|
25
|
+
|
|
26
|
+
# before create or update
|
|
27
|
+
before_save do
|
|
28
|
+
if changes.keys.include? first_association_builder.belongs_to.foreign_key
|
|
29
|
+
if send(first_association_builder.belongs_to.foreign_key).nil?
|
|
30
|
+
new_value = nil
|
|
31
|
+
else
|
|
32
|
+
dependee_record = first_association_builder._quickery_dependee_record(self)
|
|
33
|
+
new_value = dependee_record.send(dependee_column_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
assign_attributes(depender_column_name => new_value)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# add callback to sync changes when dependee_column has been updated
|
|
43
|
+
def add_callback_to_dependee_model
|
|
44
|
+
last_association_builder = @quickery_builder.last_association_builder
|
|
45
|
+
depender_column_name = @quickery_builder.depender_column_name
|
|
46
|
+
dependee_column_name = @quickery_builder.dependee_column_name
|
|
47
|
+
|
|
48
|
+
last_association_builder.model.class_exec do
|
|
49
|
+
|
|
50
|
+
before_update do
|
|
51
|
+
if changes.keys.include? dependee_column_name
|
|
52
|
+
new_value = send(dependee_column_name)
|
|
53
|
+
|
|
54
|
+
dependent_records = last_association_builder._quickery_dependent_records(self)
|
|
55
|
+
dependent_records.update_all(depender_column_name => new_value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
before_destroy do
|
|
60
|
+
if attributes.keys.include? dependee_column_name
|
|
61
|
+
new_value = nil
|
|
62
|
+
|
|
63
|
+
dependent_records = last_association_builder._quickery_dependent_records(self)
|
|
64
|
+
dependent_records.update_all(depender_column_name => new_value)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# also add callbacks to sync changes when intermediary associations have been changed (this does not include first and last builder)
|
|
71
|
+
def add_callback_to_each_intermediate_model
|
|
72
|
+
last_association_builder = @quickery_builder.last_association_builder
|
|
73
|
+
depender_column_name = @quickery_builder.depender_column_name
|
|
74
|
+
dependee_column_name = @quickery_builder.dependee_column_name
|
|
75
|
+
|
|
76
|
+
last_association_builder._quickery_get_parent_builders(include_self: true)[1..-2].each do |association_builder|
|
|
77
|
+
intermediate_model = association_builder.model
|
|
78
|
+
|
|
79
|
+
intermediate_model.class_exec do
|
|
80
|
+
|
|
81
|
+
before_update do
|
|
82
|
+
if changes.keys.include? association_builder.belongs_to.foreign_key
|
|
83
|
+
if send(association_builder.belongs_to.foreign_key).nil?
|
|
84
|
+
new_value = nil
|
|
85
|
+
else
|
|
86
|
+
dependee_record = association_builder._quickery_dependee_record(self)
|
|
87
|
+
new_value = dependee_record.send(dependee_column_name)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
dependent_records = association_builder._quickery_dependent_records(self)
|
|
91
|
+
dependent_records.update_all(depender_column_name => new_value)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
before_destroy do
|
|
96
|
+
if attributes.keys.include? association_builder.belongs_to.foreign_key
|
|
97
|
+
new_value = nil
|
|
98
|
+
|
|
99
|
+
dependent_records = last_association_builder._quickery_dependent_records(self)
|
|
100
|
+
dependent_records.update_all(depender_column_name => new_value)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Quickery
|
|
2
|
+
class QuickeryBuilder
|
|
3
|
+
attr_reader :model
|
|
4
|
+
attr_reader :depender_column_name
|
|
5
|
+
attr_reader :dependee_column_name
|
|
6
|
+
attr_reader :first_association_builder
|
|
7
|
+
attr_reader :last_association_builder
|
|
8
|
+
attr_reader :callbacks_builder
|
|
9
|
+
|
|
10
|
+
def initialize(dependee_column_name:, last_association_builder:)
|
|
11
|
+
@dependee_column_name = dependee_column_name
|
|
12
|
+
@last_association_builder = last_association_builder
|
|
13
|
+
@first_association_builder = last_association_builder._quickery_get_parent_builders.last
|
|
14
|
+
@model = @first_association_builder.model
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ==(depender_column_name)
|
|
18
|
+
@depender_column_name = depender_column_name
|
|
19
|
+
|
|
20
|
+
@callbacks_builder = CallbacksBuilder.new(quickery_builder: self)
|
|
21
|
+
|
|
22
|
+
define_quickery_builders_in_model_class unless @model.respond_to? :quickery_builders
|
|
23
|
+
|
|
24
|
+
# include this to the list of quickery builders defined for this model
|
|
25
|
+
@model.quickery_builders[depender_column_name] = self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def define_quickery_builders_in_model_class
|
|
31
|
+
# set default empty Hash if first time setting quickery_builders
|
|
32
|
+
@model.class_eval do
|
|
33
|
+
@quickery_builders = {}
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
attr_reader :quickery_builders
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/quickery.rb
ADDED
data/quickery.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'quickery/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'quickery'
|
|
8
|
+
spec.version = Quickery::VERSION
|
|
9
|
+
spec.authors = ['Jules Roman Polidario']
|
|
10
|
+
spec.email = ['jrpolidario@gmail.com']
|
|
11
|
+
|
|
12
|
+
spec.summary = 'Database Anti-normalization pattern implementing Law of Demeter by mapping associated record attributes as own attributes (one-way read-only), and therefore improves query speeds at the cost of slower writes.'
|
|
13
|
+
spec.description = 'Implements Law of Demeter by mapping associated record attributes as own attributes (one-way read-only). Consequently, speeds up SQL queries by removing joins queries between intermediary models, at the cost of slower writes. This is an anti-normalization pattern in favour of actual data-redundancy and faster queries. Use this only as necessary.'
|
|
14
|
+
spec.homepage = 'https://github.com/jrpolidario/quickery'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
|
19
|
+
end
|
|
20
|
+
spec.bindir = 'exe'
|
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
|
+
spec.require_paths = ['lib']
|
|
23
|
+
|
|
24
|
+
spec.add_dependency 'activerecord', '~> 4.0'
|
|
25
|
+
spec.add_dependency 'activesupport', '~> 4.0'
|
|
26
|
+
|
|
27
|
+
spec.add_development_dependency 'byebug', '~> 9.0'
|
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.7.0'
|
|
31
|
+
spec.add_development_dependency 'rspec-rails', '~> 3.7.2'
|
|
32
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3.13'
|
|
33
|
+
spec.add_development_dependency 'database_cleaner', '~> 1.7.0'
|
|
34
|
+
spec.add_development_dependency 'faker', '~> 1.9.1'
|
|
35
|
+
spec.add_development_dependency 'combustion', '~> 0.9.1'
|
|
36
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.7.3'
|
|
37
|
+
spec.add_development_dependency 'appraisal', '~> 2.2.0'
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: quickery
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jules Roman Polidario
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-09-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '4.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '4.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '4.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: byebug
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '9.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '9.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: bundler
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.16'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.16'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rake
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '10.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '10.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 3.7.0
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 3.7.0
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec-rails
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: 3.7.2
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 3.7.2
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: sqlite3
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 1.3.13
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: 1.3.13
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: database_cleaner
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: 1.7.0
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: 1.7.0
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: faker
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 1.9.1
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 1.9.1
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: combustion
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: 0.9.1
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: 0.9.1
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: guard-rspec
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: 4.7.3
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: 4.7.3
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: appraisal
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - "~>"
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: 2.2.0
|
|
188
|
+
type: :development
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - "~>"
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: 2.2.0
|
|
195
|
+
description: Implements Law of Demeter by mapping associated record attributes as
|
|
196
|
+
own attributes (one-way read-only). Consequently, speeds up SQL queries by removing
|
|
197
|
+
joins queries between intermediary models, at the cost of slower writes. This is
|
|
198
|
+
an anti-normalization pattern in favour of actual data-redundancy and faster queries.
|
|
199
|
+
Use this only as necessary.
|
|
200
|
+
email:
|
|
201
|
+
- jrpolidario@gmail.com
|
|
202
|
+
executables: []
|
|
203
|
+
extensions: []
|
|
204
|
+
extra_rdoc_files: []
|
|
205
|
+
files:
|
|
206
|
+
- ".gitignore"
|
|
207
|
+
- ".rspec"
|
|
208
|
+
- ".travis.yml"
|
|
209
|
+
- Appraisals
|
|
210
|
+
- Gemfile
|
|
211
|
+
- Gemfile.lock
|
|
212
|
+
- Guardfile
|
|
213
|
+
- LICENSE.txt
|
|
214
|
+
- README.md
|
|
215
|
+
- Rakefile
|
|
216
|
+
- bin/console
|
|
217
|
+
- bin/setup
|
|
218
|
+
- config.ru
|
|
219
|
+
- developer_guide.md
|
|
220
|
+
- gemfiles/.bundle/config
|
|
221
|
+
- gemfiles/rails_3.gemfile
|
|
222
|
+
- gemfiles/rails_4.gemfile
|
|
223
|
+
- gemfiles/rails_5.gemfile
|
|
224
|
+
- lib/quickery.rb
|
|
225
|
+
- lib/quickery/active_record_extensions/dsl.rb
|
|
226
|
+
- lib/quickery/association_builder.rb
|
|
227
|
+
- lib/quickery/callbacks_builder.rb
|
|
228
|
+
- lib/quickery/quickery_builder.rb
|
|
229
|
+
- lib/quickery/version.rb
|
|
230
|
+
- quickery.gemspec
|
|
231
|
+
homepage: https://github.com/jrpolidario/quickery
|
|
232
|
+
licenses:
|
|
233
|
+
- MIT
|
|
234
|
+
metadata: {}
|
|
235
|
+
post_install_message:
|
|
236
|
+
rdoc_options: []
|
|
237
|
+
require_paths:
|
|
238
|
+
- lib
|
|
239
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
240
|
+
requirements:
|
|
241
|
+
- - ">="
|
|
242
|
+
- !ruby/object:Gem::Version
|
|
243
|
+
version: '0'
|
|
244
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
|
+
requirements:
|
|
246
|
+
- - ">="
|
|
247
|
+
- !ruby/object:Gem::Version
|
|
248
|
+
version: '0'
|
|
249
|
+
requirements: []
|
|
250
|
+
rubyforge_project:
|
|
251
|
+
rubygems_version: 2.7.6
|
|
252
|
+
signing_key:
|
|
253
|
+
specification_version: 4
|
|
254
|
+
summary: Database Anti-normalization pattern implementing Law of Demeter by mapping
|
|
255
|
+
associated record attributes as own attributes (one-way read-only), and therefore
|
|
256
|
+
improves query speeds at the cost of slower writes.
|
|
257
|
+
test_files: []
|