calculated_attributes 0.1.3 → 0.1.4
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/.ruby-version +1 -0
- data/README.md +11 -3
- data/calculated_attributes.gemspec +0 -1
- data/circle.yml +3 -0
- data/gemfiles/rails3.gemfile.lock +4 -4
- data/gemfiles/rails4_1.gemfile.lock +4 -4
- data/gemfiles/rails4_2.gemfile.lock +4 -4
- data/lib/calculated_attributes/model_methods.rb +16 -8
- data/lib/calculated_attributes/version.rb +1 -1
- data/spec/lib/calculated_attributes_spec.rb +15 -0
- data/spec/support/data.rb +4 -2
- data/spec/support/models.rb +1 -0
- metadata +5 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31b050a3311b7b6c70f23b201f4761b103b6137f
|
4
|
+
data.tar.gz: 875255b28e9c8e597b4bf0d912f3f02283544d18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9dd904985c4d11aa47ecbc53e90b9500f5a427437ec934e8666ffdb29db88c129151843899f93dad9dc6f9aa884903933ad529b0f5bf03c8e15ad5a23e718f18
|
7
|
+
data.tar.gz: 493f1937397278a71be5743c39160f12582ebe30a93530de9e9138454423e475259fc72e0d1c26d800314a4a5b39f7d956443855827bd014c9a05bc2658c54c2
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
[](https://badge.fury.io/rb/calculated_attributes)
|
1
|
+
[](https://badge.fury.io/rb/calculated_attributes)
|
2
|
+
[](https://circleci.com/gh/aha-app/calculated_attributes)
|
2
3
|
|
3
4
|
# CalculatedAttributes
|
4
5
|
|
@@ -22,7 +23,7 @@ Or install it yourself as:
|
|
22
23
|
|
23
24
|
## Usage
|
24
25
|
|
25
|
-
Add each calculated attribute to your model using the `calculated` keyword. It accepts two parameters: a symbol representing the name of the calculated attribute, and a lambda containing a string to calculate the attribute.
|
26
|
+
Add each calculated attribute to your model using the `calculated` keyword. It accepts two parameters: a symbol representing the name of the calculated attribute, and a lambda containing a string to calculate the attribute. The lambda can accept arguments.
|
26
27
|
|
27
28
|
For example, if we have two models, `Post` and `Comment`, and `Comment` has a `post_id` attribute, we might write the following code to add a comments count to each `Post` record in a relation:
|
28
29
|
|
@@ -30,6 +31,7 @@ For example, if we have two models, `Post` and `Comment`, and `Comment` has a `p
|
|
30
31
|
class Post < ActiveRecord::Base
|
31
32
|
...
|
32
33
|
calculated :comments_count, -> { "select count(*) from comments where comments.post_id = posts.id" }
|
34
|
+
calculated :comments_count_by_user, ->(user) { ["select count(*) from comments where comments.post_id = posts.id and posts.user_id = '%s'", user.id] }
|
33
35
|
...
|
34
36
|
end
|
35
37
|
```
|
@@ -38,6 +40,7 @@ Then, the comments count may be accessed as follows:
|
|
38
40
|
|
39
41
|
```ruby
|
40
42
|
Post.scoped.calculated(:comments_count).first.comments_count
|
43
|
+
Post.scoped.calculated(comments_count_by_user: user).first.comments_count_by_user
|
41
44
|
#=> 5
|
42
45
|
```
|
43
46
|
|
@@ -69,6 +72,9 @@ You may also use the `calculated` method on a single model instance, like so:
|
|
69
72
|
```ruby
|
70
73
|
Post.first.calculated(:comments_count).comments_count
|
71
74
|
#=> 5
|
75
|
+
|
76
|
+
Post.first.calculated(comments_count_by_user: user).comments_count_by_user
|
77
|
+
#=> 0
|
72
78
|
```
|
73
79
|
|
74
80
|
If you have defined a `calculated` method, results of that method will be returned rather than throwing a method missing error even if you don't explicitly use the `calculated()` call on the instance:
|
@@ -76,6 +82,8 @@ If you have defined a `calculated` method, results of that method will be return
|
|
76
82
|
```ruby
|
77
83
|
Post.first.comments_count
|
78
84
|
#=> 5
|
85
|
+
Post.first.comments_count_by_user(user)
|
86
|
+
#=> 0
|
79
87
|
```
|
80
88
|
|
81
89
|
If you like, you may define `calculated` lambdas using Arel syntax:
|
@@ -108,4 +116,4 @@ will error. This is because of an [ActiveRecord issue](https://github.com/rails/
|
|
108
116
|
|
109
117
|
## Credits
|
110
118
|
|
111
|
-
Written by Zach Schneider based on ideas from Chris Waters.
|
119
|
+
Written by Zach Schneider based on ideas from Chris Waters.
|
@@ -18,7 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ['lib']
|
19
19
|
|
20
20
|
spec.add_development_dependency 'appraisal', '~> 1.0.3'
|
21
|
-
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
21
|
spec.add_development_dependency 'rake', '~> 10.0'
|
23
22
|
spec.add_development_dependency 'rspec', '~> 3.1'
|
24
23
|
spec.add_development_dependency 'rubocop', '~> 0.32.0'
|
data/circle.yml
ADDED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
calculated_attributes (0.1.
|
4
|
+
calculated_attributes (0.1.4)
|
5
5
|
activerecord (>= 3.2.20)
|
6
6
|
|
7
7
|
GEM
|
@@ -27,7 +27,6 @@ GEM
|
|
27
27
|
astrolabe (1.3.0)
|
28
28
|
parser (>= 2.2.0.pre.3, < 3.0)
|
29
29
|
builder (3.0.4)
|
30
|
-
byebug (6.0.2)
|
31
30
|
diff-lcs (1.2.5)
|
32
31
|
i18n (0.7.0)
|
33
32
|
multi_json (1.11.1)
|
@@ -66,10 +65,11 @@ PLATFORMS
|
|
66
65
|
DEPENDENCIES
|
67
66
|
activerecord (= 3.2.21)
|
68
67
|
appraisal (~> 1.0.3)
|
69
|
-
bundler (~> 1.7)
|
70
|
-
byebug
|
71
68
|
calculated_attributes!
|
72
69
|
rake (~> 10.0)
|
73
70
|
rspec (~> 3.1)
|
74
71
|
rubocop (~> 0.32.0)
|
75
72
|
sqlite3 (~> 1.3.10)
|
73
|
+
|
74
|
+
BUNDLED WITH
|
75
|
+
1.11.2
|
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
calculated_attributes (0.1.
|
4
|
+
calculated_attributes (0.1.4)
|
5
5
|
activerecord (>= 3.2.20)
|
6
6
|
|
7
7
|
GEM
|
@@ -29,7 +29,6 @@ GEM
|
|
29
29
|
astrolabe (1.3.0)
|
30
30
|
parser (>= 2.2.0.pre.3, < 3.0)
|
31
31
|
builder (3.2.2)
|
32
|
-
byebug (6.0.2)
|
33
32
|
diff-lcs (1.2.5)
|
34
33
|
i18n (0.7.0)
|
35
34
|
json (1.8.3)
|
@@ -71,10 +70,11 @@ PLATFORMS
|
|
71
70
|
DEPENDENCIES
|
72
71
|
activerecord (= 4.1.11)
|
73
72
|
appraisal (~> 1.0.3)
|
74
|
-
bundler (~> 1.7)
|
75
|
-
byebug
|
76
73
|
calculated_attributes!
|
77
74
|
rake (~> 10.0)
|
78
75
|
rspec (~> 3.1)
|
79
76
|
rubocop (~> 0.32.0)
|
80
77
|
sqlite3 (~> 1.3.10)
|
78
|
+
|
79
|
+
BUNDLED WITH
|
80
|
+
1.11.2
|
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
calculated_attributes (0.1.
|
4
|
+
calculated_attributes (0.1.4)
|
5
5
|
activerecord (>= 3.2.20)
|
6
6
|
|
7
7
|
GEM
|
@@ -29,7 +29,6 @@ GEM
|
|
29
29
|
astrolabe (1.3.0)
|
30
30
|
parser (>= 2.2.0.pre.3, < 3.0)
|
31
31
|
builder (3.2.2)
|
32
|
-
byebug (6.0.2)
|
33
32
|
diff-lcs (1.2.5)
|
34
33
|
i18n (0.7.0)
|
35
34
|
json (1.8.3)
|
@@ -71,10 +70,11 @@ PLATFORMS
|
|
71
70
|
DEPENDENCIES
|
72
71
|
activerecord (= 4.2.2)
|
73
72
|
appraisal (~> 1.0.3)
|
74
|
-
bundler (~> 1.7)
|
75
|
-
byebug
|
76
73
|
calculated_attributes!
|
77
74
|
rake (~> 10.0)
|
78
75
|
rspec (~> 3.1)
|
79
76
|
rubocop (~> 0.32.0)
|
80
77
|
sqlite3 (~> 1.3.10)
|
78
|
+
|
79
|
+
BUNDLED WITH
|
80
|
+
1.11.2
|
@@ -42,9 +42,9 @@ class ActiveRecord::Base
|
|
42
42
|
self.class.base_class
|
43
43
|
end
|
44
44
|
if class_with_attr.respond_to? :scoped
|
45
|
-
class_with_attr.scoped.calculated(sym).find(id).send(sym)
|
45
|
+
class_with_attr.scoped.calculated(sym => args).find(id).send(sym)
|
46
46
|
else
|
47
|
-
class_with_attr.all.calculated(sym).find(id).send(sym)
|
47
|
+
class_with_attr.all.calculated(sym => args).find(id).send(sym)
|
48
48
|
end
|
49
49
|
else
|
50
50
|
super(sym, *args, &block)
|
@@ -67,16 +67,24 @@ end
|
|
67
67
|
class ActiveRecord::Relation
|
68
68
|
def calculated(*args)
|
69
69
|
projections = arel.projections
|
70
|
-
args.
|
71
|
-
|
72
|
-
|
70
|
+
args = args.flat_map do |arg|
|
71
|
+
case arg
|
72
|
+
when Symbol then [[arg, []]]
|
73
|
+
when Hash then arg.to_a
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
args.each do |attribute, arguments|
|
78
|
+
lam = klass.calculated.calculated[attribute] || klass.base_class.calculated.calculated[attribute]
|
79
|
+
sql = lam.call(*arguments)
|
80
|
+
sql = klass.send(:sanitize_sql, *sql) if sql.is_a?(Array)
|
73
81
|
new_projection =
|
74
82
|
if sql.is_a?(String)
|
75
|
-
Arel.sql("(#{sql})").as(
|
83
|
+
Arel.sql("(#{sql})").as(attribute.to_s)
|
76
84
|
elsif sql.respond_to? :to_sql
|
77
|
-
Arel.sql("(#{sql.to_sql})").as(
|
85
|
+
Arel.sql("(#{sql.to_sql})").as(attribute.to_s)
|
78
86
|
else
|
79
|
-
sql.as(
|
87
|
+
sql.as(attribute.to_s)
|
80
88
|
end
|
81
89
|
new_projection.calculated_attr!
|
82
90
|
projections.push new_projection
|
@@ -13,6 +13,13 @@ describe 'calculated_attributes' do
|
|
13
13
|
expect(model_scoped(Post).calculated(:comments_count).first.comments_count).to eq(1)
|
14
14
|
end
|
15
15
|
|
16
|
+
it 'includes parametric calculated attributes' do
|
17
|
+
expect(model_scoped(Post).calculated(comments_by_user: [User.where(username: 'unused').first])
|
18
|
+
.first.comments_by_user).to eq(0)
|
19
|
+
expect(model_scoped(Post).calculated(comments_by_user: [User.where(username: 'test').first])
|
20
|
+
.first.comments_by_user).to eq(1)
|
21
|
+
end
|
22
|
+
|
16
23
|
it 'includes multiple calculated attributes' do
|
17
24
|
post = model_scoped(Post).calculated(:comments_count, :comments_two).first
|
18
25
|
expect(post.comments_count).to eq(1)
|
@@ -37,10 +44,18 @@ describe 'calculated_attributes' do
|
|
37
44
|
expect(Post.first.calculated(:comments_count).comments_count).to eq(1)
|
38
45
|
end
|
39
46
|
|
47
|
+
it 'allows access via model instance method with parameters' do
|
48
|
+
expect(Post.first.calculated(comments_by_user: [User.where(username: 'test').first]).comments_by_user).to eq(1)
|
49
|
+
end
|
50
|
+
|
40
51
|
it 'allows anonymous access via model instance method' do
|
41
52
|
expect(Post.first.comments_count).to eq(1)
|
42
53
|
end
|
43
54
|
|
55
|
+
it 'allows access via model instance method with parameters' do
|
56
|
+
expect(Post.first.comments_by_user(User.where(username: 'test').first)).to eq(1)
|
57
|
+
end
|
58
|
+
|
44
59
|
it 'allows anonymous access via model instance method with STI and lambda on base class' do
|
45
60
|
expect(Tutorial.first.comments_count).to eq(1)
|
46
61
|
end
|
data/spec/support/data.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
u = User.create(username: 'test')
|
2
|
+
User.create(username: 'unused')
|
3
|
+
p = Post.create(text: 'First post!', user: u)
|
4
|
+
Comment.create(post_id: p.id, text: 'First comment!', user: u)
|
3
5
|
Post.create(text: 'Second post!')
|
4
6
|
t = Tutorial.create(text: 'Tutorial!')
|
5
7
|
Comment.create(post_id: t.id, text: 'First comment!')
|
data/spec/support/models.rb
CHANGED
@@ -2,6 +2,7 @@ class Post < ActiveRecord::Base
|
|
2
2
|
has_many :comments
|
3
3
|
belongs_to :user
|
4
4
|
|
5
|
+
calculated :comments_by_user, ->(user) { "select count(*) from comments where comments.post_id = posts.id and comments.user_id = #{user.id}" }
|
5
6
|
calculated :comments_count, -> { 'select count(*) from comments where comments.post_id = posts.id' }
|
6
7
|
calculated :comments_two, -> { 'select count(*) from comments where comments.post_id = posts.id' }
|
7
8
|
calculated :comments_arel, -> { Comment.where(Comment.arel_table[:post_id].eq(Post.arel_table[:id])).select(Arel.sql('count(*)')) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: calculated_attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Schneider
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: appraisal
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.0.3
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: bundler
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '1.7'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '1.7'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rake
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,12 +104,14 @@ files:
|
|
118
104
|
- ".gitignore"
|
119
105
|
- ".rspec"
|
120
106
|
- ".rubocop.yml"
|
107
|
+
- ".ruby-version"
|
121
108
|
- Appraisals
|
122
109
|
- Gemfile
|
123
110
|
- LICENSE.txt
|
124
111
|
- README.md
|
125
112
|
- Rakefile
|
126
113
|
- calculated_attributes.gemspec
|
114
|
+
- circle.yml
|
127
115
|
- gemfiles/rails3.gemfile
|
128
116
|
- gemfiles/rails3.gemfile.lock
|
129
117
|
- gemfiles/rails4_1.gemfile
|
@@ -162,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
150
|
version: '0'
|
163
151
|
requirements: []
|
164
152
|
rubyforge_project:
|
165
|
-
rubygems_version: 2.
|
153
|
+
rubygems_version: 2.5.1
|
166
154
|
signing_key:
|
167
155
|
specification_version: 4
|
168
156
|
summary: Automatically add calculated attributes to ActiveRecord models.
|