operable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
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,11 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'bundler'
4
+ require 'rspec/core/rake_task'
5
+
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ desc 'Run all specs in the spec directory'
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ task :default => :spec
@@ -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
@@ -0,0 +1,3 @@
1
+ module Operable
2
+ VERSION = '0.1.0'
3
+ 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: []