compute 1.0.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.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +87 -0
- data/Rakefile +8 -0
- data/compute.gemspec +28 -0
- data/lib/compute.rb +57 -0
- data/lib/compute/computation.rb +40 -0
- data/lib/compute/computation_graph.rb +70 -0
- data/lib/compute/railtie.rb +9 -0
- data/lib/compute/version.rb +3 -0
- data/spec/compute/compute_spec.rb +245 -0
- data/spec/spec_helper.rb +9 -0
- metadata +141 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Venkat Dinavahi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Compute
|
2
|
+
|
3
|
+
Compute is like Excel for your ActiveRecord models.
|
4
|
+
It lets you define computed attributes stored that get stored the database.
|
5
|
+
|
6
|
+
The main benefits are
|
7
|
+
- Performance: computed columns are only updated when the values they depend on change
|
8
|
+
- Querying: you can now easy include these columns in your queries
|
9
|
+
|
10
|
+
Here are some sample use cases
|
11
|
+
- Having an field which depends on the users birthday. Now you can easily query on age instead of having custom SQL.
|
12
|
+
- An SHA1 hash that gets updated when the a file path changes
|
13
|
+
- A bill could have tax, tip, and total which all depend on subtotal
|
14
|
+
- Having a city and state column get computed from longitude and latitude (using the Google Geocoding API)
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
gem 'compute'
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install compute
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
The compute method accepts a block.
|
33
|
+
The names of the arguments in the block MUST match up with the properties of the model.
|
34
|
+
The following example keeps the user.age column in sync with the user.time column.
|
35
|
+
|
36
|
+
```
|
37
|
+
class User < ActiveRecord::Base
|
38
|
+
|
39
|
+
compute :age do |birthday|
|
40
|
+
unless birthday.blank?
|
41
|
+
now = Time.now.utc.to_date
|
42
|
+
now.year - birthday.year - ((now.month > birthday.month || (now.month == birthday.month && now.day >= birthday.day)) ? 0 : 1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
The compute method accepts multiple arguments. Computations are also run in the correct order.
|
50
|
+
In the block, self will be set to the model instance.
|
51
|
+
In the following example, total will be calculated after tax and tip are calculated. Again, think of it like Excel!
|
52
|
+
|
53
|
+
```
|
54
|
+
class RestaurantBill < ActiveRecord::Base
|
55
|
+
|
56
|
+
compute(:date) { |created_at| created_at.to_date }
|
57
|
+
|
58
|
+
compute :total do |tax, tip|
|
59
|
+
tax + tip
|
60
|
+
end
|
61
|
+
|
62
|
+
compute :tax do |subtotal|
|
63
|
+
subtotal * tax_rate
|
64
|
+
end
|
65
|
+
|
66
|
+
compute :tip do |subtotal|
|
67
|
+
subtotal * tip_rate
|
68
|
+
end
|
69
|
+
|
70
|
+
def tax_rate
|
71
|
+
0.08
|
72
|
+
end
|
73
|
+
|
74
|
+
def tip_rate
|
75
|
+
0.15
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
## Contributing
|
82
|
+
|
83
|
+
1. Fork it
|
84
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
85
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
86
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/compute.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/compute/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Venkat Dinavahi"]
|
6
|
+
gem.email = ["vendiddy@gmail.com"]
|
7
|
+
gem.description = %q{ActiveRecord extension for providing computed fields.}
|
8
|
+
gem.summary = %q{
|
9
|
+
Provides a compute DSL for creating ActiveRecord columns that are computed from other ones.
|
10
|
+
Automatically keeps the columns up to date.
|
11
|
+
}
|
12
|
+
gem.homepage = ""
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.name = "compute"
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.version = Compute::VERSION
|
20
|
+
gem.required_ruby_version = '>= 1.9.3'
|
21
|
+
|
22
|
+
gem.add_dependency "activerecord"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rspec"
|
25
|
+
gem.add_development_dependency "sqlite3"
|
26
|
+
gem.add_development_dependency "debugger"
|
27
|
+
gem.add_development_dependency "with_model"
|
28
|
+
end
|
data/lib/compute.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "compute/version"
|
2
|
+
require "compute/railtie" if defined? Rails
|
3
|
+
|
4
|
+
require 'compute/computation'
|
5
|
+
require 'compute/computation_graph'
|
6
|
+
|
7
|
+
module Compute
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def compute(property, &block)
|
12
|
+
computations << Computation.new(self, property, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def computations
|
16
|
+
@computations ||= ComputationGraph.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def recompute!(*properties)
|
20
|
+
scoped.each { |record| record.recompute!(*properties) }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
base.extend ClassMethods
|
27
|
+
base.class_eval do
|
28
|
+
around_save :execute_outdated_computations
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def recompute!(*properties)
|
33
|
+
properties = properties.compact.flatten
|
34
|
+
if properties.empty?
|
35
|
+
self.class.computations.each_in_order { |c| c.execute(self) }
|
36
|
+
else
|
37
|
+
properties.each do |property|
|
38
|
+
computation = self.class.computations.for_property(property)
|
39
|
+
computation.execute(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
save
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def execute_outdated_computations
|
48
|
+
yield
|
49
|
+
|
50
|
+
each_outdated_computation { |computation| computation.execute(self) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def each_outdated_computation
|
54
|
+
self.class.computations.for_changed_properties(self.changed).each { |c| yield c }
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Compute
|
2
|
+
|
3
|
+
class Computation
|
4
|
+
|
5
|
+
attr_accessor :property
|
6
|
+
|
7
|
+
def initialize(model, property, &block)
|
8
|
+
@model = model
|
9
|
+
@property = property
|
10
|
+
@proc = Proc.new(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def dependencies
|
14
|
+
@dependencies ||= calculate_dependencies(@proc)
|
15
|
+
end
|
16
|
+
|
17
|
+
def needs_update?(changed_properties)
|
18
|
+
common_changes = changed_properties.map(&:to_sym) & @dependencies
|
19
|
+
common_changes.count > 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute(record)
|
23
|
+
source_values = source_values(record)
|
24
|
+
destination_result = record.instance_exec(*source_values, &@proc)
|
25
|
+
record.send(@property.to_s + '=', destination_result)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def calculate_dependencies(proc)
|
31
|
+
proc.parameters.map { |arg| arg[1] }
|
32
|
+
end
|
33
|
+
|
34
|
+
def source_values(record)
|
35
|
+
@dependencies.map { |property| record.send(property) }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'tsort'
|
2
|
+
|
3
|
+
module Compute
|
4
|
+
|
5
|
+
class CyclicComputation < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ComputationGraph < Hash
|
9
|
+
include TSort
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@sorted_computations = []
|
13
|
+
@computations_for_changed_properties = Hash.new do |h, changed_properties|
|
14
|
+
@sorted_computations.select { |c| c.needs_update?(changed_properties) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(computation)
|
19
|
+
self[computation.property] = computation
|
20
|
+
sort!
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_in_order
|
24
|
+
@sorted_computations.each { |c| yield c }
|
25
|
+
end
|
26
|
+
|
27
|
+
def for_property(property)
|
28
|
+
self[property.to_sym]
|
29
|
+
end
|
30
|
+
|
31
|
+
def for_changed_properties(changed_properties)
|
32
|
+
@computations_for_changed_properties[changed_properties]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def sort!
|
38
|
+
begin
|
39
|
+
@sorted_computations = tsort.map { |p| self[p] }.compact
|
40
|
+
rescue TSort::Cyclic => e
|
41
|
+
raise CyclicComputation, "You have a circular dependency. (#{circular_dependency_string})"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def circular_dependency_string
|
46
|
+
circular_dependencies.map do |dependencies|
|
47
|
+
dependencies << dependencies.first
|
48
|
+
dependencies.join(' -> ')
|
49
|
+
end.join(', ')
|
50
|
+
end
|
51
|
+
|
52
|
+
def circular_dependencies
|
53
|
+
strongly_connected_components.select { |x| x.count > 1 }
|
54
|
+
end
|
55
|
+
|
56
|
+
def tsort_each_node
|
57
|
+
each do |_, computation|
|
58
|
+
yield computation.property
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def tsort_each_child(property)
|
63
|
+
if self.has_key?(property)
|
64
|
+
self[property].dependencies.each { |p| yield p }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Compute do
|
4
|
+
|
5
|
+
with_model :User do
|
6
|
+
table do |t|
|
7
|
+
t.string :first_name
|
8
|
+
t.string :last_name
|
9
|
+
t.string :full_name
|
10
|
+
t.string :first_initial
|
11
|
+
t.string :initials
|
12
|
+
t.date :date
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
model do
|
17
|
+
include Compute
|
18
|
+
|
19
|
+
compute :first_initial do |first_name|
|
20
|
+
first_name[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
compute :full_name do |first_name, last_name|
|
24
|
+
"#{first_name} #{last_name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
compute :date do |created_at|
|
28
|
+
created_at.to_date
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
with_model :Bill do
|
36
|
+
table do |t|
|
37
|
+
t.integer :subtotal
|
38
|
+
t.integer :tax
|
39
|
+
t.integer :tip
|
40
|
+
t.integer :total
|
41
|
+
end
|
42
|
+
|
43
|
+
model do
|
44
|
+
include Compute
|
45
|
+
|
46
|
+
# total is put first to ensure dependency tracker correctly sorts
|
47
|
+
compute :total do |subtotal, tax, tip|
|
48
|
+
subtotal + tax + tip
|
49
|
+
end
|
50
|
+
|
51
|
+
compute :tax do |subtotal|
|
52
|
+
(subtotal * 0.05).to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
compute :tip do |subtotal|
|
56
|
+
(subtotal * 0.15).to_i
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
with_model :ModelWithCycles do
|
62
|
+
|
63
|
+
table do |t|
|
64
|
+
t.integer :a
|
65
|
+
t.integer :b
|
66
|
+
end
|
67
|
+
|
68
|
+
model do
|
69
|
+
include Compute
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
it "should work when updated as a field" do
|
76
|
+
u = User.new
|
77
|
+
u.first_name = "George"
|
78
|
+
u.save
|
79
|
+
|
80
|
+
u.first_initial.should == 'G'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should work when added in the constructor" do
|
84
|
+
u = User.create(first_name: "Wally")
|
85
|
+
u.first_initial.should == 'W'
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should allow multiple sources for a computed field" do
|
89
|
+
u = User.create(first_name: "John", last_name: "Doe")
|
90
|
+
u.full_name.should == "John Doe"
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should work with timestamps" do
|
94
|
+
u = User.create(first_name: "John", last_name: "Doe")
|
95
|
+
u.created_at.should_not be_nil
|
96
|
+
u.date.should_not be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should raise a CyclicComputation error when there is a circular dependency" do
|
100
|
+
expect do
|
101
|
+
class ModelWithCycles
|
102
|
+
compute(:a) { |b| b * 2 }
|
103
|
+
compute(:b) { |a| a / 2 }
|
104
|
+
end
|
105
|
+
end.to raise_error(Compute::CyclicComputation)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should update the computed field if even one of the values changes" do
|
109
|
+
u = User.create(first_name: "John", last_name: "Doe")
|
110
|
+
|
111
|
+
u.first_name = "Bob"
|
112
|
+
u.save
|
113
|
+
u.full_name.should == "Bob Doe"
|
114
|
+
|
115
|
+
u.last_name = "Schmoe"
|
116
|
+
u.save
|
117
|
+
u.full_name.should == "Bob Schmoe"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should take dependencies into consideration" do
|
121
|
+
restaurant_bill = Bill.create(subtotal: 100)
|
122
|
+
restaurant_bill.tax.should == 5
|
123
|
+
restaurant_bill.tip.should == 15
|
124
|
+
restaurant_bill.total.should == 120
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should update dependent fields when a field is updated" do
|
128
|
+
bill = Bill.create(subtotal: 100)
|
129
|
+
|
130
|
+
bill.update_attributes(subtotal: 200)
|
131
|
+
|
132
|
+
bill.tax.should == 10
|
133
|
+
bill.tip.should == 30
|
134
|
+
bill.total.should == 240
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "recompute!" do
|
138
|
+
|
139
|
+
it "should only recompute the specified field" do
|
140
|
+
bill = Bill.create(subtotal: 100)
|
141
|
+
[:tax, :tip, :total].each { |c| bill.update_column(c, 0) }
|
142
|
+
|
143
|
+
bill.recompute!(:tax)
|
144
|
+
bill.tax.should == 5
|
145
|
+
bill.tip.should == 0
|
146
|
+
bill.total.should == 105
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should work with multiple fields regardless of order" do
|
150
|
+
bill = Bill.create(subtotal: 100)
|
151
|
+
[:tax, :tip, :total].each { |c| bill.update_column(c, 0) }
|
152
|
+
|
153
|
+
bill.tax.should == 0
|
154
|
+
bill.tip.should == 0
|
155
|
+
bill.total.should == 0
|
156
|
+
|
157
|
+
bill.recompute!(:total, :tax, :tip)
|
158
|
+
bill.tax.should == 5
|
159
|
+
bill.tip.should == 15
|
160
|
+
bill.total.should == 120
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should work with an array" do
|
164
|
+
bill = Bill.create(subtotal: 100)
|
165
|
+
[:tax, :tip, :total].each { |c| bill.update_column(c, 0) }
|
166
|
+
|
167
|
+
bill.recompute!([:tax, :tip])
|
168
|
+
bill.tax.should == 5
|
169
|
+
bill.tip.should == 15
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should propagate all changes" do
|
173
|
+
bill = Bill.create(subtotal: 100)
|
174
|
+
bill.update_column(:subtotal, 200)
|
175
|
+
|
176
|
+
bill.recompute!
|
177
|
+
bill.tax.should == 10
|
178
|
+
bill.tip.should == 30
|
179
|
+
bill.total.should == 240
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should work on multiple records for all columns" do
|
183
|
+
bill1 = Bill.create(subtotal: 100)
|
184
|
+
bill1.update_column(:tax, 0)
|
185
|
+
bill1.update_column(:tip, 0)
|
186
|
+
|
187
|
+
bill2 = Bill.create(subtotal: 200)
|
188
|
+
bill2.update_column(:tax, 0)
|
189
|
+
bill2.update_column(:tip, 0)
|
190
|
+
|
191
|
+
Bill.recompute!
|
192
|
+
bill1.reload
|
193
|
+
bill2.reload
|
194
|
+
|
195
|
+
bill1.tip.should == 15
|
196
|
+
bill2.tip.should == 30
|
197
|
+
|
198
|
+
bill1.tax.should == 5
|
199
|
+
bill2.tax.should == 10
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should work on multiple records for specific columns" do
|
203
|
+
bill1 = Bill.create(subtotal: 100)
|
204
|
+
bill1.update_column(:tax, 0)
|
205
|
+
bill1.update_column(:tip, 0)
|
206
|
+
|
207
|
+
bill2 = Bill.create(subtotal: 200)
|
208
|
+
bill2.update_column(:tax, 0)
|
209
|
+
bill2.update_column(:tip, 0)
|
210
|
+
|
211
|
+
Bill.recompute!(:tip)
|
212
|
+
bill1.reload
|
213
|
+
bill2.reload
|
214
|
+
|
215
|
+
bill1.tip.should == 15
|
216
|
+
bill2.tip.should == 30
|
217
|
+
|
218
|
+
bill1.tax.should == 0
|
219
|
+
bill2.tax.should == 0
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should work on an ActiveRecord relation" do
|
223
|
+
bill1 = Bill.create(subtotal: 100)
|
224
|
+
bill1.update_column(:tax, 0)
|
225
|
+
bill1.update_column(:tip, 0)
|
226
|
+
|
227
|
+
bill2 = Bill.create(subtotal: 200)
|
228
|
+
bill2.update_column(:tax, 0)
|
229
|
+
bill2.update_column(:tip, 0)
|
230
|
+
|
231
|
+
Bill.where(id: [bill1.id, bill2.id]).recompute!(:tip)
|
232
|
+
bill1.reload
|
233
|
+
bill2.reload
|
234
|
+
|
235
|
+
bill1.tip.should == 15
|
236
|
+
bill2.tip.should == 30
|
237
|
+
|
238
|
+
bill1.tax.should == 0
|
239
|
+
bill2.tax.should == 0
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: compute
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Venkat Dinavahi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: debugger
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: with_model
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: ActiveRecord extension for providing computed fields.
|
95
|
+
email:
|
96
|
+
- vendiddy@gmail.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- Gemfile
|
103
|
+
- LICENSE
|
104
|
+
- README.md
|
105
|
+
- Rakefile
|
106
|
+
- compute.gemspec
|
107
|
+
- lib/compute.rb
|
108
|
+
- lib/compute/computation.rb
|
109
|
+
- lib/compute/computation_graph.rb
|
110
|
+
- lib/compute/railtie.rb
|
111
|
+
- lib/compute/version.rb
|
112
|
+
- spec/compute/compute_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
homepage: ''
|
115
|
+
licenses: []
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.9.3
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 1.8.24
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: Provides a compute DSL for creating ActiveRecord columns that are computed
|
138
|
+
from other ones. Automatically keeps the columns up to date.
|
139
|
+
test_files:
|
140
|
+
- spec/compute/compute_spec.rb
|
141
|
+
- spec/spec_helper.rb
|