charisma 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document CHANGED
@@ -1,11 +1,11 @@
1
- # .document is used by rdoc and yard to know how to generate documentation
2
- # for example, it can be used to control how rdoc gets built when you do `gem install foo`
3
-
4
- README.rdoc
5
- lib/**/*.rb
6
- bin/*
7
-
8
- # Files below this - are treated as 'extra files', and aren't parsed for ruby code
9
- -
10
- features/**/*.feature
11
- LICENSE
1
+ # .document is used by rdoc and yard to know how to generate documentation
2
+ # for example, it can be used to control how rdoc gets built when you do `gem install foo`
3
+
4
+ README.rdoc
5
+ lib/**/*.rb
6
+ bin/*
7
+
8
+ # Files below this - are treated as 'extra files', and aren't parsed for ruby code
9
+ -
10
+ features/**/*.feature
11
+ LICENSE
data/.gitignore CHANGED
@@ -1,40 +1,40 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
5
- # rcov generated
6
- coverage
7
-
8
- # rdoc generated
9
- rdoc
10
-
11
- # yard generated
12
- doc
13
- .yardoc
14
-
15
- # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
16
- #
17
- # * Create a file at ~/.gitignore
18
- # * Include files you want ignored
19
- # * Run: git config --global core.excludesfile ~/.gitignore
20
- #
21
- # After doing this, these files will be ignored in all your git projects,
22
- # saving you from having to 'pollute' every project you touch with them
23
- #
24
- # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
25
- #
26
- # For MacOS:
27
- #
28
- #.DS_Store
29
- #
30
- # For TextMate
31
- #*.tmproj
32
- #tmtags
33
- #
34
- # For emacs:
35
- *~
36
- #\#*
37
- #.\#*
38
- #
39
- # For vim:
40
- #*.swp
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ # rcov generated
6
+ coverage
7
+
8
+ # rdoc generated
9
+ rdoc
10
+
11
+ # yard generated
12
+ doc
13
+ .yardoc
14
+
15
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
16
+ #
17
+ # * Create a file at ~/.gitignore
18
+ # * Include files you want ignored
19
+ # * Run: git config --global core.excludesfile ~/.gitignore
20
+ #
21
+ # After doing this, these files will be ignored in all your git projects,
22
+ # saving you from having to 'pollute' every project you touch with them
23
+ #
24
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
25
+ #
26
+ # For MacOS:
27
+ #
28
+ #.DS_Store
29
+ #
30
+ # For TextMate
31
+ #*.tmproj
32
+ #tmtags
33
+ #
34
+ # For emacs:
35
+ *~
36
+ #\#*
37
+ #.\#*
38
+ #
39
+ # For vim:
40
+ #*.swp
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2011 Andy Rossmeissl
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.
1
+ Copyright (c) 2011 Andy Rossmeissl
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.
@@ -0,0 +1,152 @@
1
+ # Charisma
2
+
3
+ Charisma provides a *superficiality framework* for Ruby objects. You can use it to:
4
+
5
+ * Provide a *curation strategy* for your class that defines which of its attributes are *superfically important.*
6
+ * Define *metadata* on these characteristics, such as measurements and units.
7
+ * Facilitate *appropriate presentation* of these characteristics (i.e., intelligent `#to_s`).
8
+
9
+ Charisma is all about how your objects look to the outside world. We use it within [CM1](http://carbon.brighterplanet.com) to facilitate the characterization of the flights, cars, etc. that we're performing carbon calculations on.
10
+
11
+ See also: [RDocs](http://rubydoc.info/github/brighterplanet/charisma/master/frames).
12
+
13
+ ## Example
14
+
15
+ ``` ruby
16
+ class Spaceship < SuperModel::Base # or ActiveRecord, or whatever
17
+ attributes :window_count, :name, :size, :weight
18
+ belongs_to :make, :class_name => 'SpaceshipMake', :primary_key => 'name'
19
+ belongs_to :fuel, :class_name => 'SpaceshipFuel', :primary_key => 'name'
20
+ belongs_to :destination, :class_name => 'Planet', :primary_key => 'name'
21
+
22
+ include Charisma
23
+ characterize do
24
+ has :make, :display_with => :name
25
+ has :fuel
26
+ has :window_count do |window_count|
27
+ "#{window_count} windows"
28
+ end
29
+ has :size, :measures => :length # uses Charisma::Measurements::Length
30
+ has :weight, :measures => RelativeAstronomicMass
31
+ has :name
32
+ has :destination
33
+ end
34
+ end
35
+ ```
36
+ ``` irb
37
+ irb(main):001:0> amaroq = Spaceship.new :size => 100, :window_count => 3, :make => SpaceshipMake.create(:name => 'Ford')
38
+ => #<Spaceship:0xacd9ac4 ...>
39
+ irb(main):002:0> amaroq.characteristics[:size].to_s
40
+ => "100 m"
41
+ irb(main):003:0> amaroq.characteristics[:size].feet
42
+ => 328.08399000000003
43
+ irb(main):004:0> amaroq.characteristics[:window_count].to_s
44
+ => "3 windows"
45
+ irb(main):005:0> amaroq.characteristics[:make].to_s
46
+ => "Ford"
47
+ ```
48
+
49
+ ## Characterizing your class
50
+
51
+ Charisma uses a DSL within a `characterize` block to define your class's attribute curation strategy:
52
+
53
+ ``` ruby
54
+ class Foo
55
+ # ...
56
+ include Charisma
57
+ characterize do
58
+ # Characteristics go here
59
+ end
60
+ end
61
+ ```
62
+
63
+ ### Simple (scalar) characteristics
64
+
65
+ How do you define characteristics? The simplest way looks like this:
66
+
67
+ ``` ruby
68
+ characterize do
69
+ has :color
70
+ end
71
+ ```
72
+
73
+ Charisma will obtain the `color` characteristic for an instance by calling its `#color` method.
74
+
75
+ ``` irb
76
+ irb(main):001:0> Foo.new(:color => 'Blue').characteristics[:color].to_s
77
+ => Blue
78
+ ```
79
+
80
+ ### Fancy characteristics
81
+
82
+ Of course, this isn't extremely useful. Things get more interesting when you want the characteristic to present itself usefully:
83
+
84
+ ``` ruby
85
+ characterize do
86
+ has :color do |color|
87
+ "##{Color.find_by_name(color).hex}"
88
+ end
89
+ end
90
+ ```
91
+ ``` irb
92
+ irb(main):001:0> Foo.new(:color => 'black').characteristics[:color].to_s
93
+ => #000000
94
+ ```
95
+
96
+ Or perhaps you're using an association:
97
+
98
+ ``` ruby
99
+ characterize do
100
+ has :color, :display_with => :hex # If unspecified, Charisma will try #as_characteristic and #to_s on the associated object, in that order
101
+ end
102
+ ```
103
+ ``` irb
104
+ irb(main):001:0> Foo.new(:color => Color.find_by_name('black')).characteristics[:color].to_s
105
+ => #000000
106
+ ```
107
+
108
+ ### Measured characteristics
109
+
110
+ Some characteristics represent measured values like *length*. You can specify that like so:
111
+
112
+ ``` ruby
113
+ characterize do
114
+ has :size, :measures => Length
115
+ end
116
+ ```
117
+ ``` ruby
118
+ class Length < Charisma::Measurement # Don't need to inherit if you want to DIY
119
+ units :meters => 'm'
120
+ end
121
+ ```
122
+ ``` irb
123
+ irb(main):001:0> Foo.new(:size => 10).characteristics[:size].to_s
124
+ => 10 m
125
+ ```
126
+
127
+ Charisma has some [convenient measurements](https://github.com/brighterplanet/charisma/tree/master/lib/charisma/measurement) baked in:
128
+
129
+ ``` ruby
130
+ characterize do
131
+ has :size, :measures => Charisma::Measurement::Length
132
+ end
133
+ ```
134
+
135
+ Built-in measurements can be referenced with a shortcut:
136
+
137
+ ``` ruby
138
+ characterize do
139
+ has :size, :measures => :length
140
+ end
141
+ ```
142
+
143
+ Charisma uses [Conversions](https://github.com/seamusabshere/conversions) to provide useful unit conversion methods:
144
+
145
+ ``` irb
146
+ irb(main):001:0> Foo.new(:size => 10).characteristics[:size].feet
147
+ => 32.808399000000003
148
+ ```
149
+
150
+ ## Copyright
151
+
152
+ Copyright (c) 2011 Andy Rossmeissl. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,46 +1,46 @@
1
- require 'rubygems'
2
-
3
- begin
4
- require 'bundler'
5
- rescue LoadError
6
- $stderr.puts "You must install bundler - run `gem install bundler`"
7
- end
8
-
9
- begin
10
- Bundler.setup
11
- rescue Bundler::BundlerError => e
12
- $stderr.puts e.message
13
- $stderr.puts "Run `bundle install` to install missing gems"
14
- exit e.status_code
15
- end
16
- require 'rake'
17
-
18
- require 'bueller'
19
- Bueller::Tasks.new
20
-
21
- require 'rake/testtask'
22
- Rake::TestTask.new(:test) do |test|
23
- test.libs << 'lib' << 'test'
24
- test.pattern = 'test/**/test_*.rb'
25
- test.verbose = true
26
- end
27
-
28
- require 'rcov/rcovtask'
29
- Rcov::RcovTask.new do |test|
30
- test.libs << 'test'
31
- test.pattern = 'test/**/test_*.rb'
32
- test.verbose = true
33
- end
34
-
35
- task :default => :test
36
-
37
- require 'rake/rdoctask'
38
- Rake::RDocTask.new do |rdoc|
39
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
-
41
- rdoc.main = 'README.rdoc'
42
- rdoc.rdoc_dir = 'rdoc'
43
- rdoc.title = "charisma #{version}"
44
- rdoc.rdoc_files.include('README*')
45
- rdoc.rdoc_files.include('lib/**/*.rb')
46
- end
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue LoadError
6
+ $stderr.puts "You must install bundler - run `gem install bundler`"
7
+ end
8
+
9
+ begin
10
+ Bundler.setup
11
+ rescue Bundler::BundlerError => e
12
+ $stderr.puts e.message
13
+ $stderr.puts "Run `bundle install` to install missing gems"
14
+ exit e.status_code
15
+ end
16
+ require 'rake'
17
+
18
+ require 'bueller'
19
+ Bueller::Tasks.new
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ task :default => :test
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.main = 'README.rdoc'
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "charisma #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
@@ -1,38 +1,38 @@
1
- $:.push File.expand_path("../lib", __FILE__)
2
- require 'charisma/version'
3
-
4
- Gem::Specification.new do |s|
5
- s.name = 'charisma'
6
- s.version = Charisma::VERSION
7
- s.platform = Gem::Platform::RUBY
8
- s.date = "2011-05-11"
9
- s.authors = ['Andy Rossmeissl', 'Seamus Abshere']
10
- s.email = 'andy@rossmeissl.net'
11
- s.homepage = 'http://github.com/brighterplanet/charisma'
12
- s.summary = %Q{Curate your rich Ruby objects' attributes}
13
- s.description = %Q{Define strategies for accessing and displaying a subset of your classes' attributes}
14
- s.extra_rdoc_files = [
15
- 'LICENSE',
16
- 'README.rdoc',
17
- ]
18
-
19
- s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7')
20
- s.rubygems_version = '1.3.7'
21
- s.specification_version = 3
22
-
23
- s.files = `git ls-files`.split("\n")
24
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
- s.require_paths = ['lib']
27
-
28
- s.add_dependency 'activesupport'
29
- s.add_dependency 'blockenspiel'
30
- s.add_dependency 'conversions'
31
-
32
- s.add_development_dependency 'bundler'
33
- s.add_development_dependency 'bueller'
34
- s.add_development_dependency 'rake'
35
- s.add_development_dependency 'rcov'
36
- s.add_development_dependency 'supermodel'
37
- end
38
-
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'charisma/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'charisma'
6
+ s.version = Charisma::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.date = "2011-06-02"
9
+ s.authors = ['Andy Rossmeissl', 'Seamus Abshere']
10
+ s.email = 'andy@rossmeissl.net'
11
+ s.homepage = 'http://github.com/brighterplanet/charisma'
12
+ s.summary = %Q{Curate your rich Ruby objects' attributes}
13
+ s.description = %Q{Define strategies for accessing and displaying a subset of your classes' attributes}
14
+ s.extra_rdoc_files = [
15
+ 'LICENSE',
16
+ 'README.markdown',
17
+ ]
18
+
19
+ s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7')
20
+ s.rubygems_version = '1.3.7'
21
+ s.specification_version = 3
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ['lib']
27
+
28
+ s.add_dependency 'activesupport'
29
+ s.add_dependency 'blockenspiel'
30
+ s.add_dependency 'conversions'
31
+
32
+ s.add_development_dependency 'bundler'
33
+ s.add_development_dependency 'bueller'
34
+ s.add_development_dependency 'rake'
35
+ s.add_development_dependency 'rcov'
36
+ s.add_development_dependency 'supermodel'
37
+ end
38
+
@@ -21,9 +21,19 @@ require 'charisma/curator'
21
21
  require 'charisma/curator/curation'
22
22
  require 'charisma/number_helper'
23
23
 
24
+ # Charisma provides a <em>superficiality framework</em> for Ruby objects.
25
+ #
26
+ # You can use it to:
27
+ #
28
+ # * Provide a <em>curation strategy</em> for your class that defines which of its attributes are <em>superfically important.</em>
29
+ # * Define <em>metadata</em> on these characteristics, such as measurements and units.
30
+ # * Facilitate <em>appropriate presentation</em> of these characteristics (i.e., intelligent <tt>#to_s</tt>).
24
31
  module Charisma
32
+ # Prepare a class for characterization with <tt>include Charisma</tt>.
25
33
  def self.included(base)
26
34
  base.send :include, Base
27
35
  base.extend Base::ClassMethods
28
36
  end
29
37
  end
38
+
39
+ Conversions.register :meters, :feet, 3.2808399
@@ -1,9 +1,12 @@
1
1
  module Charisma
2
+ # This module is included in characterized objects (via <tt>include Charisma</tt> on the class)
2
3
  module Base
4
+ # The accessor for the object's characteristics, which can be treated more or less like a Hash.'
3
5
  def characteristics
4
6
  @curator ||= Curator.new(self)
5
7
  end
6
8
  end
7
9
 
10
+ # Poorly-defined measurements raise this error.
8
11
  class InvalidMeasurementError < StandardError; end
9
12
  end
@@ -1,10 +1,19 @@
1
1
  module Charisma
2
2
  module Base
3
+ # Methods included on the characterized class
3
4
  module ClassMethods
5
+ # Establishes a <tt>@@characterization</tt> class variable along with a <tt>#characterization</tt> accessor on the class.
6
+ #
7
+ # @see Charisma::Characterizaion
4
8
  def self.extended(base)
5
9
  base.send :class_variable_set, :@@characterization, Characterization.new
6
10
  base.send :cattr_reader, :characterization, :instance_reader => false
7
11
  end
12
+
13
+ # Define a characterization on the class.
14
+ #
15
+ # The definition occurs within the block, using <tt>Charisma::Characterization#has</tt> as a DSL.
16
+ # @see Charisma::Characterization#has
8
17
  def characterize(&blk)
9
18
  Blockenspiel.invoke(blk, characterization) if block_given?
10
19
  end
@@ -1,7 +1,33 @@
1
1
  module Charisma
2
+ # Stores information about a characteristic.
3
+ #
4
+ # Typically these are defined with <tt>Charisma::Characterization#has</tt> in a <tt>characterize do . . . end</tt> block.
5
+ # @see Charisma::Base::ClassMethods#characterize
6
+ # @see Charisma::Characterization#has
2
7
  class Characteristic
3
- attr_reader :name, :proc, :accessor, :measurement
8
+ # The characteristic's name
9
+ attr_reader :name
4
10
 
11
+ # A proc, if defined, that specifies how the characteristic should be displayed
12
+ attr_reader :proc
13
+
14
+ # A method that should be called on the characteristic in order to display it properly
15
+ attr_reader :accessor
16
+
17
+ # Specifies that the characteristic should be treated as a measured quantity
18
+ attr_reader :measurement
19
+
20
+ # Create a characteristic.
21
+ #
22
+ # Typically this is done via <tt>Charisma::Characterization#has</tt> within a <tt>characterize do . . . end</tt> block.
23
+ # @param [Symbol] name The name of the characteristic. The method with this name will be called on the characterized object to retrieve its raw value.
24
+ # @param [Hash] options The options hash.
25
+ # @option [Symbol] display_with A symbol that gets sent as a method on the characteristic to retrieve its display value.
26
+ # @option [Class, Symbol] measures Specifies a measurement for the characteristic. Either provide a class constant that conforms to the Charisma::Measurement API, or use a symbol to specify a built-in Charisma measurement.
27
+ # @option [Proc] blk A proc that defines how the characteristic should be displayed.
28
+ # @see Charisma::Base::ClassMethods#characterize
29
+ # @see Charisma::Characterization#has
30
+ # @see Charisma::Measurement
5
31
  def initialize(name, options, &blk)
6
32
  @name = name
7
33
  @proc = blk if block_given?
@@ -1,6 +1,21 @@
1
1
  module Charisma
2
- class Characterization < Hash
2
+ # Stores the set of characteristics defined on a class.
3
+ #
4
+ # Typically this is created with <tt>Charisma::Base::ClassMethods#characterize</tt>, and characteristics defined within it by the ensuing block.
5
+ class Characterization < Hash
3
6
  include Blockenspiel::DSL
7
+ # Define a characteristic.
8
+ #
9
+ # This is used within <tt>Charisma::Base::ClassMethods#characterize</tt> blocks to curate attributes on a class. Internally, a <tt>Charisma::Characteristic</tt> is created to store the definition.
10
+ # @param [Symbol]
11
+ # @param [Symbol] name The name of the characteristic. The method with this name will be called on the characterized object to retrieve its raw value.
12
+ # @param [Hash] options The options hash.
13
+ # @option [Symbol] display_with A symbol that gets sent as a method on the characteristic to retrieve its display value.
14
+ # @option [Class, Symbol] measures Specifies a measurement for the characteristic. Either provide a class constant that conforms to the Charisma::Measurement API, or use a symbol to specify a built-in Charisma measurement.
15
+ # @option [Proc] blk A proc that defines how the characteristic should be displayed.
16
+ # @see Charisma::Base::ClassMethods#characterize
17
+ # @see Charisma::Characteristic
18
+ # @see Charisma::Measurement
4
19
  def has(name, options = {}, &blk)
5
20
  name = name.to_sym
6
21
  self[name] = Characteristic.new(name, options, &blk)
@@ -1,32 +1,58 @@
1
1
  require 'forwardable'
2
2
  module Charisma
3
+ # A Hash-like object that stores computed characteristics about an instance of a characterized class.
4
+ #
5
+ # Every instance of a characterized class gets a Curator, accessed via <tt>#characteristics</tt>.
6
+ # @see Charisma::Base#characteristics
3
7
  class Curator
4
- attr_reader :subject, :characteristics
8
+ # The curator's subject--the instance itself'
9
+ attr_reader :subject
5
10
 
11
+ # The hashed wrapped by the curator that actually stores the computed characteristics'
12
+ attr_reader :characteristics
13
+
14
+ # Create a Curator.
15
+ #
16
+ # Typically this is done automatically when <tt>#characteristics</tt> is called on an instance of a characterized class for the first time.
17
+ # @param [Object] The subject of the curation -- an instance of a characterized class
18
+ # @see Charisma::Base#characteristics
6
19
  def initialize(subject)
7
20
  @characteristics = {}.extend LooseEquality
8
21
  @subject = subject
9
22
  subject.class.characterization.keys.each do |key|
10
- if value = subject.send(key)
23
+ if subject.respond_to?(key) and value = subject.send(key)
11
24
  self[key] = value
12
25
  end
13
26
  end
14
27
  end
15
28
 
29
+ # Store a late-defined characteristic, with a Charisma wrapper.
30
+ # @param [Symbol] key The name of the characteristic, which must match one defined in the class's characterization
31
+ # @param value The value of the characteristic
16
32
  def []=(key, value)
17
33
  characteristics[key] = Curation.new value, subject.class.characterization[key]
18
34
  end
19
35
 
36
+ # A custom inspection method to enhance IRB and log readability
20
37
  def inspect
21
38
  "<Charisma:Curator #{keys.length} known characteristic(s)>"
22
39
  end
23
40
 
41
+ # (see #inpsect)
24
42
  def to_s; inspect end
25
43
 
26
44
  extend Forwardable
27
- delegate [:[], :keys, :slice, :==] => :characteristics
45
+ delegate [:[], :keys, :slice, :==, :each] => :characteristics
28
46
 
47
+ # Loose equality for Hashlike objects
48
+ #
49
+ # <tt>Hash#==</tt> as defined in Ruby core is very, very strict: it uses <tt>Object#==</tt> on each of its values to determine equality.
50
+ # This module overrides <tt>#==</tt> to loosen this evaluation.
29
51
  module LooseEquality
52
+ # Determine equality with another Hashlike object, loosely.
53
+ #
54
+ # Unlike Ruby's own <tt>Hash#==</tt>, this method uses the values' native <tt>#==</tt> methods, where available, to determine equality.
55
+ # @param [Hash] other The other hash
30
56
  def ==(other)
31
57
  return false unless keys.sort == other.keys.sort
32
58
  keys.all? do |k|
@@ -1,11 +1,25 @@
1
1
  require 'delegate'
2
2
  module Charisma
3
3
  class Curator
4
+ # A wrapper around computed characteristic values that intervenes when the value is asked to present itself.
5
+ #
6
+ # Implements the Delegator pattern, with uncaught methods delegated to the characteristic's computed value.
7
+ # (Undocumented below is that <tt>#to_s</tt> is specifically delegated to <tt>#render</tt>.)
4
8
  class Curation < Delegator
5
9
  extend Forwardable
10
+
11
+ # The computed value of the characteristic
6
12
  attr_accessor :value
13
+
14
+ # The characteristic, as defined in the class's characterization
15
+ # @return [Charisma::Characteristic]
7
16
  attr_reader :characteristic
8
17
 
18
+ # Create new Curation wrapper.
19
+ #
20
+ # This is typically done by the instance's <tt>Charisma::Curator</tt>
21
+ # @param value The computed value of the characteristic for the instance
22
+ # @param [Charisma::Characteristic, optional] characteristic The associated characteristic definition
9
23
  def initialize(value, characteristic = nil)
10
24
  @characteristic = characteristic
11
25
  establish_units_methods if characteristic && characteristic.measurement
@@ -14,16 +28,24 @@ module Charisma
14
28
 
15
29
  delegate [:to_s] => :render
16
30
 
31
+ # An inspection method for more readable IRB sessions and logs.
17
32
  def inspect
18
- "<Charisma::Curator::Curation for :#{characteristic.name} (use #to_s to render value of #{value.class})>"
33
+ if characteristic
34
+ "<Charisma::Curator::Curation for :#{characteristic.name} (use #to_s to render value of #{value.class})>"
35
+ else
36
+ "<Charisma::Curator::Curation for undefined characteristic (use #to_s to render value of #{value.class})>"
37
+ end
19
38
  end
20
39
 
21
- # Delegator methods
40
+ # Delegator method
22
41
  def __getobj__; value end
42
+
43
+ # Delegator method
23
44
  def __setobj__(obj); self.value = obj end
24
45
 
25
46
  private
26
47
 
48
+ # If this curation deals with a measured characteristic, this method will delegate <tt>#u</tt>, <tt>#units</tt>, and appropriate unit-name methods to <tt>#render</tt>.
27
49
  def establish_units_methods
28
50
  self.class.delegate [:u, :units] => :render
29
51
  if conversions = Conversions.conversions[units.to_sym]
@@ -31,6 +53,8 @@ module Charisma
31
53
  end
32
54
  end
33
55
 
56
+ # Provide a display-friendly presentation of the computed characteristic's value.
57
+ # @return [String]
34
58
  def render
35
59
  return value unless characteristic
36
60
  if characteristic.proc
@@ -46,18 +70,23 @@ module Charisma
46
70
  end
47
71
  end
48
72
 
73
+ # Render the value using the characteristic's proc.
49
74
  def render_proc
50
75
  characteristic.proc.call(value)
51
76
  end
52
77
 
78
+ # Render the value using an accessor on the characteristic's association.
53
79
  def use_accessor
54
80
  value.send(characteristic.accessor)
55
81
  end
56
82
 
83
+ # Render the value using its specified measurement.
57
84
  def defer_to_measurement
58
85
  measurement_class.new(value)
59
86
  end
60
87
 
88
+ # The subclass of <tt>Charisma::Measurement</tt> with which this curation's characteristic is measured.
89
+ # @return [Class]
61
90
  def measurement_class
62
91
  case characteristic.measurement
63
92
  when Class
@@ -69,10 +98,12 @@ module Charisma
69
98
  end
70
99
  end
71
100
 
101
+ # Render the value using its <tt>#as_characteristic</tt> method.
72
102
  def defer_to_value
73
103
  value.as_characteristic
74
104
  end
75
105
 
106
+ # Render the value using its <tt>#to_s</tt> method.
76
107
  def render_value
77
108
  value
78
109
  end
@@ -1,27 +1,45 @@
1
1
  module Charisma
2
+ # An abstract class that implements an API for Charisma to deal with measured characteristics.
3
+ #
4
+ # @abstract An actual measurement class should inherit from this and use <tt>#units</tt> to define units.
2
5
  class Measurement
6
+ # The quantity of the measured value
3
7
  attr_reader :value
4
8
 
9
+ # Create a new instance of this measurement.
10
+ #
11
+ # Typically this will be done automatically by <tt>Charisma::Curator::Curation</tt>.
12
+ # @param [Fixnum] value The quantity of the measured value
13
+ # @see Charisma::Curator::Curation
5
14
  def initialize(value)
6
15
  @value = value
7
16
  end
8
17
 
18
+ # Show the measured value, along with units
19
+ # @return [String]
9
20
  def to_s
10
21
  "#{NumberHelper.delimit value} #{u}"
11
22
  end
12
23
 
24
+ # Return just the quantity of the measurement
25
+ # @return [Fixnum]
13
26
  def to_f
14
27
  value.to_f
15
28
  end
16
29
 
30
+ # The measurement's units
31
+ # @return [Symbol]'
17
32
  def units
18
33
  self.class.unit
19
34
  end
20
35
 
36
+ # The standard abbreviation for the measurement's units
37
+ # @return [String]
21
38
  def u
22
39
  self.class.unit_abbreviation
23
40
  end
24
41
 
42
+ # Handle conversion methods
25
43
  def method_missing(*args)
26
44
  if Conversions.conversions[units.to_sym][args.first]
27
45
  to_f.send(units.to_sym).to(args.first)
@@ -31,14 +49,22 @@ module Charisma
31
49
  end
32
50
 
33
51
  class << self
52
+ # Define the units used with this measurement.
53
+ #
54
+ # Used by conforming subclasses.
55
+ # @param [Hash] units The units, given in the form <tt>:plural_unit_name => 'abbrev'</tt>
34
56
  def units(units)
35
57
  @units, @units_abbreviation = units.to_a.flatten
36
58
  end
37
59
 
60
+ # Retrive the units used by the measurement
61
+ # @return [Symbol]
38
62
  def unit
39
63
  @units
40
64
  end
41
65
 
66
+ # Retrieve the abbreviation of the units used by the measurement
67
+ # @return [String]
42
68
  def unit_abbreviation
43
69
  @units_abbreviation
44
70
  end
@@ -1,5 +1,6 @@
1
1
  module Charisma
2
2
  class Measurement
3
+ # Length, in meters.
3
4
  class Length < Measurement
4
5
  units :meters => 'm'
5
6
  end
@@ -0,0 +1,8 @@
1
+ module Charisma
2
+ class Measurement
3
+ # Mass, in kilograms
4
+ class Mass < Measurement
5
+ units :kilograms => 'kg'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module Charisma
2
+ class Measurement
3
+ # Speed, in meters per second
4
+ class Speed < Measurement
5
+ units :meters_per_second => 'm/s'
6
+ end
7
+ # Velocity is an SI-accepted alias for speed
8
+ Velocity = Speed
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module Charisma
2
+ class Measurement
3
+ # Time, in seconds
4
+ class Time < Measurement
5
+ units :seconds => 's'
6
+ end
7
+ end
8
+ end
@@ -1,5 +1,11 @@
1
1
  module Charisma
2
+ # Various methods for dealing with numbers
2
3
  class NumberHelper
4
+ # Delimit a number with commas and return as a string.
5
+ #
6
+ # Adapted from ActionView.
7
+ # @param [Fixnum] number The number to delimit
8
+ # @return [String]
3
9
  def self.delimit(number)
4
10
  parts = number.to_s.split('.')
5
11
  parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
@@ -1,3 +1,4 @@
1
- module Charisma
2
- VERSION = "0.2.0"
3
- end
1
+ module Charisma
2
+ # Charisma's version
3
+ VERSION = "0.3.0"
4
+ end
@@ -28,10 +28,14 @@ class Spaceship < SuperModel::Base
28
28
  has :window_count do |window_count|
29
29
  "#{window_count} windows"
30
30
  end
31
+ end
32
+
33
+ characterize do
31
34
  has :size, :measures => :length # uses Charisma::Measurements::Length
32
35
  has :weight, :measures => RelativeAstronomicMass
33
36
  has :name
34
37
  has :destination
38
+ has :color
35
39
  end
36
40
  end
37
41
 
@@ -66,11 +66,33 @@ class TestCharisma < Test::Unit::TestCase
66
66
  assert_equal [:name].sort_by { |k| k.to_s }, spaceship.characteristics.keys.sort_by { |k| k.to_s }
67
67
  end
68
68
  def test_014_characterization_keys
69
- assert_equal [:destination, :fuel, :make, :name, :size, :weight, :window_count].sort_by { |k| k.to_s }, Spaceship.characterization.keys.sort_by { |k| k.to_s }
69
+ assert_equal [:color, :destination, :fuel, :make, :name, :size, :weight, :window_count].sort_by { |k| k.to_s }, Spaceship.characterization.keys.sort_by { |k| k.to_s }
70
70
  end
71
71
  def test_015_characteristic_equality
72
72
  amaroq = Spaceship.new :window_count => 8
73
73
  buster = Spaceship.new :window_count => 8
74
74
  assert_equal amaroq.characteristics, buster.characteristics
75
75
  end
76
+ def test_016_inspection_of_undefined_characteristic
77
+ muktruk = Spaceship.new
78
+ muktruk.characteristics[:bumper_sticker] = 'LT'
79
+ assert_nothing_raised do
80
+ muktruk.characteristics[:bumper_sticker].inspect
81
+ end
82
+ end
83
+ def test_017_each
84
+ spaceship = Spaceship.new :name => 'Amaroq'
85
+ catch :blam do
86
+ spaceship.characteristics.each do |k, v|
87
+ throw :blam if k == :name
88
+ end
89
+ flunk
90
+ end
91
+ end
92
+ def test_018_double_bag
93
+ geo = SpaceshipMake.create :name => 'Geo'
94
+ g = Spaceship.new :make => geo
95
+ g.characteristics[:name] = g.characteristics[:make]
96
+ assert_equal 'Geo', g.characteristics[:name].to_s
97
+ end
76
98
  end
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: charisma
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
5
4
  prerelease:
6
- segments:
7
- - 0
8
- - 2
9
- - 0
10
- version: 0.2.0
5
+ version: 0.3.0
11
6
  platform: ruby
12
7
  authors:
13
8
  - Andy Rossmeissl
@@ -16,121 +11,97 @@ autorequire:
16
11
  bindir: bin
17
12
  cert_chain: []
18
13
 
19
- date: 2011-05-11 00:00:00 -04:00
14
+ date: 2011-06-02 00:00:00 -05:00
20
15
  default_executable:
21
16
  dependencies:
22
17
  - !ruby/object:Gem::Dependency
23
- prerelease: false
24
18
  name: activesupport
25
- type: :runtime
26
- version_requirements: &id001 !ruby/object:Gem::Requirement
19
+ requirement: &id001 !ruby/object:Gem::Requirement
27
20
  none: false
28
21
  requirements:
29
22
  - - ">="
30
23
  - !ruby/object:Gem::Version
31
- hash: 3
32
- segments:
33
- - 0
34
24
  version: "0"
35
- requirement: *id001
36
- - !ruby/object:Gem::Dependency
25
+ type: :runtime
37
26
  prerelease: false
27
+ version_requirements: *id001
28
+ - !ruby/object:Gem::Dependency
38
29
  name: blockenspiel
39
- type: :runtime
40
- version_requirements: &id002 !ruby/object:Gem::Requirement
30
+ requirement: &id002 !ruby/object:Gem::Requirement
41
31
  none: false
42
32
  requirements:
43
33
  - - ">="
44
34
  - !ruby/object:Gem::Version
45
- hash: 3
46
- segments:
47
- - 0
48
35
  version: "0"
49
- requirement: *id002
50
- - !ruby/object:Gem::Dependency
36
+ type: :runtime
51
37
  prerelease: false
38
+ version_requirements: *id002
39
+ - !ruby/object:Gem::Dependency
52
40
  name: conversions
53
- type: :runtime
54
- version_requirements: &id003 !ruby/object:Gem::Requirement
41
+ requirement: &id003 !ruby/object:Gem::Requirement
55
42
  none: false
56
43
  requirements:
57
44
  - - ">="
58
45
  - !ruby/object:Gem::Version
59
- hash: 3
60
- segments:
61
- - 0
62
46
  version: "0"
63
- requirement: *id003
64
- - !ruby/object:Gem::Dependency
47
+ type: :runtime
65
48
  prerelease: false
49
+ version_requirements: *id003
50
+ - !ruby/object:Gem::Dependency
66
51
  name: bundler
67
- type: :development
68
- version_requirements: &id004 !ruby/object:Gem::Requirement
52
+ requirement: &id004 !ruby/object:Gem::Requirement
69
53
  none: false
70
54
  requirements:
71
55
  - - ">="
72
56
  - !ruby/object:Gem::Version
73
- hash: 3
74
- segments:
75
- - 0
76
57
  version: "0"
77
- requirement: *id004
78
- - !ruby/object:Gem::Dependency
58
+ type: :development
79
59
  prerelease: false
60
+ version_requirements: *id004
61
+ - !ruby/object:Gem::Dependency
80
62
  name: bueller
81
- type: :development
82
- version_requirements: &id005 !ruby/object:Gem::Requirement
63
+ requirement: &id005 !ruby/object:Gem::Requirement
83
64
  none: false
84
65
  requirements:
85
66
  - - ">="
86
67
  - !ruby/object:Gem::Version
87
- hash: 3
88
- segments:
89
- - 0
90
68
  version: "0"
91
- requirement: *id005
92
- - !ruby/object:Gem::Dependency
69
+ type: :development
93
70
  prerelease: false
71
+ version_requirements: *id005
72
+ - !ruby/object:Gem::Dependency
94
73
  name: rake
95
- type: :development
96
- version_requirements: &id006 !ruby/object:Gem::Requirement
74
+ requirement: &id006 !ruby/object:Gem::Requirement
97
75
  none: false
98
76
  requirements:
99
77
  - - ">="
100
78
  - !ruby/object:Gem::Version
101
- hash: 3
102
- segments:
103
- - 0
104
79
  version: "0"
105
- requirement: *id006
106
- - !ruby/object:Gem::Dependency
80
+ type: :development
107
81
  prerelease: false
82
+ version_requirements: *id006
83
+ - !ruby/object:Gem::Dependency
108
84
  name: rcov
109
- type: :development
110
- version_requirements: &id007 !ruby/object:Gem::Requirement
85
+ requirement: &id007 !ruby/object:Gem::Requirement
111
86
  none: false
112
87
  requirements:
113
88
  - - ">="
114
89
  - !ruby/object:Gem::Version
115
- hash: 3
116
- segments:
117
- - 0
118
90
  version: "0"
119
- requirement: *id007
120
- - !ruby/object:Gem::Dependency
91
+ type: :development
121
92
  prerelease: false
93
+ version_requirements: *id007
94
+ - !ruby/object:Gem::Dependency
122
95
  name: supermodel
123
- type: :development
124
- version_requirements: &id008 !ruby/object:Gem::Requirement
96
+ requirement: &id008 !ruby/object:Gem::Requirement
125
97
  none: false
126
98
  requirements:
127
99
  - - ">="
128
100
  - !ruby/object:Gem::Version
129
- hash: 3
130
- segments:
131
- - 0
132
101
  version: "0"
133
- requirement: *id008
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: *id008
134
105
  description: Define strategies for accessing and displaying a subset of your classes' attributes
135
106
  email: andy@rossmeissl.net
136
107
  executables: []
@@ -139,13 +110,13 @@ extensions: []
139
110
 
140
111
  extra_rdoc_files:
141
112
  - LICENSE
142
- - README.rdoc
113
+ - README.markdown
143
114
  files:
144
115
  - .document
145
116
  - .gitignore
146
117
  - Gemfile
147
118
  - LICENSE
148
- - README.rdoc
119
+ - README.markdown
149
120
  - Rakefile
150
121
  - charisma.gemspec
151
122
  - lib/charisma.rb
@@ -157,6 +128,9 @@ files:
157
128
  - lib/charisma/curator/curation.rb
158
129
  - lib/charisma/measurement.rb
159
130
  - lib/charisma/measurement/length.rb
131
+ - lib/charisma/measurement/mass.rb
132
+ - lib/charisma/measurement/speed.rb
133
+ - lib/charisma/measurement/time.rb
160
134
  - lib/charisma/number_helper.rb
161
135
  - lib/charisma/version.rb
162
136
  - test/helper.rb
@@ -175,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
149
  requirements:
176
150
  - - ">="
177
151
  - !ruby/object:Gem::Version
178
- hash: 3
152
+ hash: -405120809897582198
179
153
  segments:
180
154
  - 0
181
155
  version: "0"
@@ -184,11 +158,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
158
  requirements:
185
159
  - - ">="
186
160
  - !ruby/object:Gem::Version
187
- hash: 21
188
- segments:
189
- - 1
190
- - 3
191
- - 7
192
161
  version: 1.3.7
193
162
  requirements: []
194
163
 
@@ -197,5 +166,6 @@ rubygems_version: 1.6.2
197
166
  signing_key:
198
167
  specification_version: 3
199
168
  summary: Curate your rich Ruby objects' attributes
200
- test_files: []
201
-
169
+ test_files:
170
+ - test/helper.rb
171
+ - test/test_charisma.rb
@@ -1,17 +0,0 @@
1
- = charisma
2
-
3
- Description goes here.
4
-
5
- == Note on Patches/Pull Requests
6
-
7
- * Fork the project.
8
- * Make your feature addition or bug fix.
9
- * Add tests for it. This is important so I don't break it in a
10
- future version unintentionally.
11
- * Commit, do not mess with rakefile, version, or history.
12
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
- * Send me a pull request. Bonus points for topic branches.
14
-
15
- == Copyright
16
-
17
- Copyright (c) 2011 Andy Rossmeissl. See LICENSE for details.