charisma 0.2.0 → 0.3.0

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/.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.