operable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/MIT-LICENSE +20 -0
- data/README.md +140 -0
- data/Rakefile +11 -0
- data/lib/operable/fields.rb +25 -0
- data/lib/operable/operations.rb +54 -0
- data/lib/operable/version.rb +3 -0
- data/lib/operable.rb +37 -0
- metadata +142 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 Jason Coene
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# Operable
|
2
|
+
|
3
|
+
An easy way to perform simple operations (addition, subtraction, multiplication, division) on your models. It is tested with ActiveRecord and Mongoid and should work with any ActiveModel compliant class.
|
4
|
+
|
5
|
+
[![travis](https://secure.travis-ci.org/jcoene/operable.png)](http://travis-ci.org/jcoene/operable)
|
6
|
+
|
7
|
+
## A Simple Example
|
8
|
+
|
9
|
+
Let's say we're collecting records of Baseball games. A `Team` model has various stats like `W`, `L`, `T`, etc.
|
10
|
+
|
11
|
+
Operable, once included in your models, lets you use the `+`, `-`, `*` and `/` operators on them, just as you would an Integer. It returns in the value of the class you're already working with.
|
12
|
+
|
13
|
+
We might imagine something like this:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
# Create team instances for the 2 NY baseball teams:
|
17
|
+
yankees = Team.new :w => 5, :l => 4, :t => 1
|
18
|
+
mets = Team.new :w => 3, :l => 7, :t => 0
|
19
|
+
|
20
|
+
# Get their combined totals
|
21
|
+
yankees + mets # => #<Team w: 8, l: 11, t: 1>
|
22
|
+
|
23
|
+
# How much better are the yankees?
|
24
|
+
yankees - mets # => #<Team w: 2, l: -3, t: 1>
|
25
|
+
|
26
|
+
# If the yankees keep this up for the rest of the season...
|
27
|
+
yankees * 16.2 # => #<Team w: 81, l: 65, t: 16>
|
28
|
+
```
|
29
|
+
|
30
|
+
## Getting Started
|
31
|
+
|
32
|
+
First, add operable to your Gemfile:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'operable'
|
36
|
+
```
|
37
|
+
|
38
|
+
Next, include the module in your model and specify which fields to operate on:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# ActiveRecord:
|
42
|
+
class Team < ActiveRecord::Base
|
43
|
+
include Operable
|
44
|
+
operable_on :w, :l, :t
|
45
|
+
end
|
46
|
+
|
47
|
+
# Mongoid:
|
48
|
+
class Team
|
49
|
+
include Mongoid::Document
|
50
|
+
include Operable
|
51
|
+
field :w
|
52
|
+
field :l
|
53
|
+
field :t
|
54
|
+
operable_on :w, :l, :t
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
That's it! You can call operable_on multiple times to add more fields.
|
59
|
+
|
60
|
+
## Operations
|
61
|
+
|
62
|
+
Four operations are provided: `+`, `-`, `*`, `/`.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
red = Color.new :r => 128, :g => 0, :b => 0
|
66
|
+
green = Color.new :r => 0, :g => 128, :b => 0
|
67
|
+
blue = Color.new :r => 0, :g => 0, :b => 128
|
68
|
+
|
69
|
+
yellow = green + red # => #<Color r: 128, g: 128, b: 0>
|
70
|
+
grey = yellow + blue # => #<Color r: 128, g: 128, b: 128>
|
71
|
+
bright_red = red * 2 # => #<Color r: 255, g: 0, b: 0>
|
72
|
+
dark_grey = grey / 3 # => #<Color r: 43, g: 43, b: 43>
|
73
|
+
```
|
74
|
+
|
75
|
+
## Equality
|
76
|
+
|
77
|
+
Compare the equality of operable objects with the `matches?` method:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
purple = Color.new :r => 128, :g => 0, :b => 128
|
81
|
+
|
82
|
+
purple.matches?(red + blue) #=> true
|
83
|
+
purple.matches?(red + green) #=> false
|
84
|
+
```
|
85
|
+
|
86
|
+
### Operate On All (Mongoid only)
|
87
|
+
|
88
|
+
Mongoid models define their fields explicitly in the model declaration. We can use this to automatically determine what to operate on:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class Team
|
92
|
+
include Mongoid::Document
|
93
|
+
include Operable
|
94
|
+
field :w
|
95
|
+
field :l
|
96
|
+
field :t
|
97
|
+
operable_on_all # operates on w, l and t.
|
98
|
+
operable_on_all_except :t # operates on w and l
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
### Operate On Associations (Mongoid only)
|
103
|
+
|
104
|
+
We can include associations in our list of operable fields just as with normal attributes!
|
105
|
+
|
106
|
+
First, include Operable on **both** documents. Then, on the **parent** document, manually specify the name of the association to *operable_on* (associations are not included by *operable_on_all* methods)
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class First
|
110
|
+
include Mongoid::Document
|
111
|
+
include Operable
|
112
|
+
field :a
|
113
|
+
field :b
|
114
|
+
embeds_one :second
|
115
|
+
operable_on_all
|
116
|
+
operable_on :second
|
117
|
+
end
|
118
|
+
|
119
|
+
class Second
|
120
|
+
include Mongoid::Document
|
121
|
+
include Operable
|
122
|
+
field :c
|
123
|
+
field :d
|
124
|
+
embedded_in :first
|
125
|
+
operable_on_all
|
126
|
+
end
|
127
|
+
|
128
|
+
f1 = First.new :a => 1, :b => 2, :second => Second.new(:c => 1, :d => 2)
|
129
|
+
f2 = First.new :a => 2, :b => 3, :second => Second.new(:c => 2, :d => 3)
|
130
|
+
f3 = f1 + f2 # => #<First a: 3, b: 5>
|
131
|
+
f3.second # => #<Second c: 3, d: 5>
|
132
|
+
```
|
133
|
+
|
134
|
+
## Enhancements and Pull Requests
|
135
|
+
|
136
|
+
If you find the project useful but it doesn't meet all of your needs, feel free to fork it and send a pull request. I'd especially love contributions that enhance behavior with ActiveRecord. Please make sure to include specs!
|
137
|
+
|
138
|
+
## License
|
139
|
+
|
140
|
+
MIT license, go wild.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Operable
|
2
|
+
module Fields
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Return a list of attributes (with values) to be included in operations
|
7
|
+
#
|
8
|
+
# Returns Hash of key (string) => value pairs
|
9
|
+
def operable_values
|
10
|
+
raise "Please specify one or more fields via operable_on in your model definition!" if self.class.operable_fields.nil?
|
11
|
+
|
12
|
+
keys = attributes.keys
|
13
|
+
keys << relations.keys if respond_to?(:associations)
|
14
|
+
values = attributes.select {|k, v| self.class.operable_fields.include? k }
|
15
|
+
if respond_to?(:associations)
|
16
|
+
associations.select {|k, v| self.class.operable_fields.include? k }.each do |k, v|
|
17
|
+
values[k.to_s] = send(k)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
values
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Operable
|
2
|
+
module Operations
|
3
|
+
|
4
|
+
# Add or subtract one method from another
|
5
|
+
#
|
6
|
+
# method - name of method to perform, as a symbol ( :+ or :- )
|
7
|
+
# other - other object to operate with
|
8
|
+
#
|
9
|
+
# Returns the resulting object
|
10
|
+
def add_or_subtract(method, other)
|
11
|
+
self.class.new.tap do |o|
|
12
|
+
operable_values.each do |k,v|
|
13
|
+
o.send("#{k}=", (v || 0).send(method, (other.send(k) || 0)))
|
14
|
+
# o[k] = (v || 0).send(method, (other[k] || 0))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def +(other)
|
20
|
+
add_or_subtract(:+, other)
|
21
|
+
end
|
22
|
+
|
23
|
+
def -(other)
|
24
|
+
add_or_subtract(:-, other)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Multiply or divide values of this object by another
|
28
|
+
#
|
29
|
+
# Returns a new object
|
30
|
+
def multiply_or_divide(method, by)
|
31
|
+
self.class.new.tap do |o|
|
32
|
+
operable_values.each do |k,v|
|
33
|
+
o.send("#{k}=", (v || 0).send(method, by))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def *(by)
|
39
|
+
multiply_or_divide(:*, by)
|
40
|
+
end
|
41
|
+
|
42
|
+
def /(by)
|
43
|
+
multiply_or_divide(:/, by)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Compare equality between objects
|
47
|
+
#
|
48
|
+
# Returns true or false
|
49
|
+
def matches?(other)
|
50
|
+
operable_values == other.operable_values
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/operable.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'operable/fields'
|
3
|
+
require 'operable/operations'
|
4
|
+
require 'operable/version'
|
5
|
+
|
6
|
+
module Operable
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
include Operable::Fields
|
11
|
+
include Operable::Operations
|
12
|
+
|
13
|
+
included do
|
14
|
+
cattr_accessor :operable_fields
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
INOPERABLE_FIELDS = %w[_type _id created_at updated_at version]
|
20
|
+
|
21
|
+
def operable_on(*names)
|
22
|
+
fields = operable_fields || []
|
23
|
+
self.operable_fields = [fields, names].flatten.map(&:to_s).uniq.sort
|
24
|
+
end
|
25
|
+
|
26
|
+
def operable_on_all
|
27
|
+
raise "Unable to list all fields for this ORM" unless respond_to?(:fields)
|
28
|
+
self.operable_fields = fields.keys.reject {|k| INOPERABLE_FIELDS.include? k }
|
29
|
+
end
|
30
|
+
|
31
|
+
def operable_on_all_except(*names)
|
32
|
+
self.operable_fields = operable_on_all.reject {|k| names.map(&:to_s).include? k }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: operable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jason Coene
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-08 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activemodel
|
16
|
+
requirement: &70205376469520 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70205376469520
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activerecord
|
27
|
+
requirement: &70205376468940 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70205376468940
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mongoid
|
38
|
+
requirement: &70205376468340 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70205376468340
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bson_ext
|
49
|
+
requirement: &70205376467760 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70205376467760
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sqlite3
|
60
|
+
requirement: &70205376467180 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '1.3'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70205376467180
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: growl
|
71
|
+
requirement: &70205376466680 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70205376466680
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rspec
|
82
|
+
requirement: &70205376466020 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.6'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70205376466020
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: guard-rspec
|
93
|
+
requirement: &70205376465400 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 0.4.3
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70205376465400
|
102
|
+
description: Operable is the easiest way to perform common operations on multiple
|
103
|
+
instances of a model.
|
104
|
+
email:
|
105
|
+
- jcoene@gmail.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- lib/operable/fields.rb
|
111
|
+
- lib/operable/operations.rb
|
112
|
+
- lib/operable/version.rb
|
113
|
+
- lib/operable.rb
|
114
|
+
- MIT-LICENSE
|
115
|
+
- Rakefile
|
116
|
+
- Gemfile
|
117
|
+
- README.md
|
118
|
+
homepage: http://github.com/jcoene/operable
|
119
|
+
licenses: []
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 1.8.10
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: Convenient operations for your models.
|
142
|
+
test_files: []
|