natural_born_slugger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c53161bfaee3307f0789e14966a1cef3def933f3
4
+ data.tar.gz: b0d8d1d8e94fae0798e5ad1b6411d21ff50a18c0
5
+ SHA512:
6
+ metadata.gz: 4847802082fabbcb6f537d607f8cabf918fda7a258966c4ac0b43e9a7fe7a89794f1880a2b1a9918c800a80c1d9ab23c3fb15d2f0fca151b32eb6a5e1d443390
7
+ data.tar.gz: b93c87ae94fec058f6dac6e6027cc5dde19ab2880a7d292dfd89f5eeca71cada422cc8fbc326a3acfb3755789c5469716190bc7ac0167d943ad2d063ead921fc
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ - LICENSE.md
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in natural-born-slugger.gemspec
4
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+ ===========
3
+
4
+ Copyright (c) 2013 Christopher Keele
5
+ ------------------------------------
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,182 @@
1
+ Natural Born Slugger
2
+ ====================
3
+
4
+ A gem for managing composite attributes, especially natural keys and url slugs.
5
+
6
+ Supports automatically-updated natural keys/slugs. Has ORM and Rack extensions.
7
+
8
+
9
+
10
+ Installation
11
+ ------------
12
+
13
+ Using bundler, add to your `Gemfile`:
14
+
15
+ gem 'natural_born_slugger'
16
+
17
+ Otherwise:
18
+
19
+ gem install natural_born_slugger
20
+
21
+ and load it:
22
+
23
+ require 'natural_born_slugger'
24
+
25
+
26
+
27
+ Usage
28
+ -----
29
+
30
+ ### Composing Attributes
31
+
32
+ At its core, `NaturalBornSlugger` is a simple, well-tested DSL to compose dynamic attributes dependent on other fields:
33
+
34
+ Person = Struct.new(:first_name, :middle_name, :last_name) do
35
+ include NaturalBornSlugger
36
+ has_slug first_name: nil, middle_name: nil, last_name: nil
37
+ end
38
+
39
+ bambino = Person.new('George', 'Herman', 'Ruth')
40
+
41
+ bambino.slug #=> "GeorgeHermanRuth"
42
+
43
+ bambino.first_name, bambino.middle_name = 'Babe', nil
44
+
45
+ bambino.slug #=> "BabeRuth"
46
+
47
+
48
+ ### Setting Transformations
49
+
50
+ `NaturalBornSlugger` also lets you set tranformations on each dependency:
51
+
52
+ Person = Struct.new(:id, :name) do
53
+ include NaturalBornSlugger
54
+ has_natural_key name: :dashify, id: :hashify, join_with: '-'
55
+ end
56
+
57
+ buster = Person.new(23, 'Lou Gehrig')
58
+
59
+ buster.natural_key #=> "lou-gehrig-37693cf"
60
+
61
+ buster.name = 'The Iron Horse'
62
+
63
+ buster.natural_key #=> "the-iron-horse-37693cf"
64
+
65
+ `NaturalBornSlugger` interprets many objects as valid transformations:
66
+
67
+ ##### Symbols:
68
+ - Converts dependency to string and sends the symbol tranformation to the `String` instance.
69
+ - `NaturalBornSlugger` extends `String` with two methods:
70
+ 1. `hashify` - an alias for [ActiveSupports's parameterize method](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize)
71
+ 2. `dashify` - convert string to an MD5 hash and use the first 7 digits
72
+ - Examples: `:hashify`, `:dashify`, `:capitalize`, `:chomp`, `:downcase`, `:reverse`, `:squeeze`, `:strip`, `:upcase`, etc
73
+
74
+ ##### Strings:
75
+ - Attempts to use the string tranformation as a [format string](http://apidock.com/ruby/Kernel/sprintf) for the dependency.
76
+ - If the string tranformation isn't a format string, it will replace the dependency in the slug, which probably isn't what you want.
77
+ - Examples: `id: "%05d"`, `cost: "$%.02f"`
78
+
79
+ ##### Regexps:
80
+ - Scans the dependency using the regex tranformation and joins all matches.
81
+ - Joins matches with the same `join_with` used in the rest of the slug.
82
+ - Examples: Can't think of any uses
83
+
84
+ ##### Procs:
85
+ - Passes the dependency into the provided `lambda` or `Proc`.
86
+ - Allows custom tranformations. The `Proc` must accept one parameter and return a string or nil.
87
+ - Examples: compacting arrays of related objects, ie: `followers: ->(followers){ "%05d" % followers.count }`
88
+
89
+ ##### nil, false, or anything else:
90
+ - Convert dependency to string and leave untouched.
91
+ - `nil` is preferred.
92
+
93
+
94
+ ### Composed Attribute Options
95
+
96
+ After including `NaturalBornSlugger` into your class, you can define an composite attribute through one of three methods:
97
+
98
+ - `has_slug`
99
+ - `has_natural_key`
100
+ - `has_composed_attribute`
101
+
102
+ Each of these methods accepts the same arguments: a name and a hash of options. `has_slug` and `has_natural_key` will use default names of `'slug'` and `'natural_key'` respectively if you do not supply one. `has_composed_attribute` requires the name.
103
+
104
+ The options hash is where you define the attribute's dependencies. It also uses special keys to customize the attribute's behavior:
105
+
106
+ ##### `:join_with`
107
+ - String to use for joining dependencies.
108
+ - Default: ""
109
+ - Example:
110
+
111
+ ```
112
+ Person = Struct.new(:first_name, :middle_name, :last_name) do
113
+ include NaturalBornSlugger
114
+ has_slug first_name: nil, middle_name: nil, last_name: nil, join_with: '-'
115
+ end
116
+
117
+ bambino = Person.new('Babe', nil, 'Ruth')
118
+ bambino.slug #=> "Babe-Ruth"
119
+ ```
120
+
121
+ ##### `:compact`
122
+ - Removes nil dependencies before joining.
123
+ - Default: true
124
+ - Example: Notice the extra dash in the example below:
125
+
126
+ ```
127
+ Person = Struct.new(:first_name, :middle_name, :last_name) do
128
+ include NaturalBornSlugger
129
+ has_slug first_name: nil, middle_name: nil, last_name: nil, compact: false, join_with: '-'
130
+ end
131
+
132
+ bambino = Person.new('Babe', nil, 'Ruth')
133
+ bambino.slug #=> "Babe--Ruth"
134
+ ```
135
+
136
+ ##### `:require_all`
137
+ - Checks to make sure all attributes exist before composing attribute.
138
+ - Makes attribute return nil until all dependencies exist.
139
+ - Default: false
140
+ - Example:
141
+
142
+ ```
143
+ Person = Struct.new(:first_name, :middle_name, :last_name) do
144
+ include NaturalBornSlugger
145
+ has_slug first_name: nil, middle_name: nil, last_name: nil, require_all: true
146
+ end
147
+
148
+ bambino = Person.new('Babe', nil, 'Ruth')
149
+ bambino.slug #=> nil
150
+
151
+ bambino.first_name, bambino.middle_name = 'George', 'Herman'
152
+ bambino.slug #=> "George-Herman-Ruth"
153
+ ```
154
+
155
+ ##### `:track`
156
+ - Provides a callback function `#{attribute_name}_change` to allow you to persist old slugs as you please.
157
+ - Default: false
158
+ - Example:
159
+
160
+ ```
161
+ Person = Struct.new(:first_name, :middle_name, :last_name) do
162
+ include NaturalBornSlugger
163
+ has_slug first_name: nil, middle_name: nil, last_name: nil, track: true
164
+
165
+ attr_accessor :formerly_known_as
166
+ def slug_change(old_slug, new_slug)
167
+ @formerly_known_as ||= []
168
+ @formerly_known_as << old_slug
169
+ end
170
+ end
171
+
172
+ bambino = Person.new('George', 'Herman', 'Ruth')
173
+ bambino.slug #=> "GeorgeHermanRuth"
174
+
175
+ bambino.first_name, bambino.middle_name = 'Babe', nil
176
+ bambino.slug #=> "BabeRuth"
177
+ bambino.formerly_known_as #=> ["GeorgeHermanRuth"]
178
+ ```
179
+
180
+ ##### _Note:_
181
+
182
+ If you happen to have methods or attributes called `join_with`, `compact`, `require_all`, or `track`, you can still build composite attributes out of them. Just use a string instead of a symbol when declaring your attribute's dependencies. `NaturalBornSlugger` only looks for symbols when reading your dependencies for these settings.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,101 @@
1
+ module NaturalBornSlugger
2
+ module ClassMethods
3
+
4
+ ##
5
+ # Adds a slug to the class. Name is optional.
6
+ #
7
+ def has_slug(name, options={})
8
+ # Use default name if not provided
9
+ name, options = 'slug', name if name.is_a?(Hash)
10
+ dependencies, options = extract_composite_dependencies(name, options)
11
+ build_composite_attribute(name.to_s, dependencies, options)
12
+ end
13
+
14
+ ##
15
+ # Adds a natural key to the class. Name is optional.
16
+ #
17
+ def has_natural_key(name, options={})
18
+ # Use default name if not provided
19
+ name, options = 'natural_key', name if name.is_a?(Hash)
20
+ dependencies, options = extract_composite_dependencies(name, options)
21
+ build_composite_attribute(name.to_s, dependencies, options)
22
+ end
23
+
24
+ ##
25
+ # Adds a generic composite attribute to the class. Name is required.
26
+ #
27
+ def has_composite_attribute(name, options={})
28
+ dependencies, options = extract_composite_dependencies(name, options)
29
+ build_composite_attribute(name.to_s, dependencies, options)
30
+ end
31
+
32
+ private
33
+
34
+ ##
35
+ # Pulls method behavior options out of general options. The remaining options
36
+ # are assumed to be the dependent attributes that compose this one.
37
+ #
38
+ def extract_composite_dependencies(name, options={})
39
+ dependencies, options = options, {}
40
+
41
+ options[:compact_dependencies] = dependencies.delete(:compact) || true
42
+ options[:require_dependencies] = dependencies.delete(:require_all) || false
43
+ options[:track_changes] = dependencies.delete(:track) || false
44
+ options[:joiner] = dependencies.delete(:join_with) || ''
45
+
46
+ raise ConfigurationError.new(self.name, name, "no dependent attributes were specified") if dependencies.empty?
47
+
48
+ [dependencies, options]
49
+ end
50
+
51
+ ##
52
+ # Adds a composite attribute to the class,
53
+ # with a getter and updater method.
54
+ # Also adds a setter that either calls the updater
55
+ # or throws an error, depending on configuration.
56
+ #
57
+ def build_composite_attribute(name, dependencies, options)
58
+
59
+ # Define instance attribute getter
60
+ define_method name do
61
+ self.send "update_#{name}"
62
+ end
63
+
64
+ # Define instance attribute setter: ignores assignment and just triggers an update
65
+ define_method "#{name}=" do |value|
66
+ if NaturalBornSlugger.configuration.ignore_attribute_setters
67
+ # TODO: Log discarded value warning
68
+ self.send "update_#{name}"
69
+ else
70
+ raise IllegalOperationError.new(self.class.name, "#{name}=", 'you cannot set composite attributes directly')
71
+ end
72
+ end
73
+
74
+ # Update instance attribute
75
+ define_method "update_#{name}" do
76
+ # Check existence of all attribute dependencies if `require_dependencies` is true
77
+ resolved_dependencies = dependencies.map do |dependency, strategy|
78
+ [resolve_dependency(dependency), strategy]
79
+ end
80
+ if options[:require_dependencies] ? resolved_dependencies.map(&:first).all? : true
81
+ new_value = self.compose_attribute(resolved_dependencies, options)
82
+ if options[:track_changes]
83
+ old_value = self.instance_variable_get("@#{name}")
84
+ unless old_value == new_value
85
+ self.send("#{name}_change".to_sym, old_value, new_value)
86
+ end
87
+ end
88
+ self.instance_variable_set("@#{name}", new_value)
89
+ end
90
+ end
91
+
92
+ if [:track_changes]
93
+ define_method "#{name}_change" do |old_value, new_value|
94
+
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,12 @@
1
+ module NaturalBornSlugger
2
+ class Configuration
3
+
4
+ attr_accessor :ignore_attribute_setters, :hashified_length
5
+
6
+ def initialize
7
+ @ignore_attribute_setters = true
8
+ @hashified_length = 7
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ module NaturalBornSlugger
2
+
3
+ class NaturalBornError < StandardError; end
4
+
5
+ class IllegalOperationError < NaturalBornError
6
+ def initialize(class_name, method_name, context = nil)
7
+ @class_name = class_name
8
+ @method_name = method_name
9
+ @context = context
10
+ @message = "Cannot call `#{class_name}.#{method_name}"
11
+ end
12
+ def to_s
13
+ [@message, @context].compact.join(': ')+'.'
14
+ end
15
+ end
16
+
17
+ class ConfigurationError < NaturalBornError
18
+ def initialize(class_name, attribute, context = nil)
19
+ @class_name = class_name
20
+ @attribute = attribute
21
+ @context = context
22
+ @message = "Composite attribute `#{attribute}` of #{class_name} has not been configured properly"
23
+ end
24
+ def to_s
25
+ [@message, @context].compact.join(': ')+'.'
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_support/inflector/transliterate'
2
+ class String
3
+
4
+ alias_method :dashify, :parameterize
5
+
6
+ def hashify
7
+ Digest::MD5.hexdigest(self).slice(0, NaturalBornSlugger.configuration.hashified_length)
8
+ end
9
+
10
+ end
@@ -0,0 +1,33 @@
1
+ module NaturalBornSlugger
2
+
3
+ def resolve_dependency(dependency)
4
+ object = self
5
+ dependency_chain = dependency.to_s.split('.')
6
+ dependency_chain.each do |method|
7
+ object = object.try :send, method
8
+ break unless object
9
+ end
10
+ object
11
+ end
12
+
13
+ def compose_attribute(resolved_dependencies, options)
14
+ resolved_dependencies.map do |resolved_dependency, strategy|
15
+ case strategy
16
+ when Symbol # Symbols represent string methods to call on the resolved dependency
17
+ resolved_dependency.to_s.send(strategy)
18
+ when String # Strings represent formats to fit the resolved dependency into
19
+ strategy % resolved_dependency
20
+ when Regexp # Regexps represent patterns to pull out of the resolved dependency and join
21
+ resolved_dependency.scan(strategy).join(options[:joiner])
22
+ when Proc # Procs should take one parameter and return a string or nil
23
+ strategy.call(resolved_dependency)
24
+ else # If no strategy provided, use resolved dependency as is
25
+ resolved_dependency.to_s
26
+ end
27
+ # Remove nil components if `compact_dependencies` is true
28
+ end.tap do |components|
29
+ components.compact! if options[:compact_dependencies]
30
+ end.join(options[:joiner])
31
+ end
32
+
33
+ end
@@ -0,0 +1,3 @@
1
+ module NaturalBornSlugger
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ require "natural_born_slugger/exceptions"
4
+ require "natural_born_slugger/configuration"
5
+
6
+ module NaturalBornSlugger
7
+
8
+ class << self
9
+ attr_accessor :configuration
10
+ end
11
+
12
+ def self.configure
13
+ self.configuration ||= Configuration.new
14
+
15
+ yield(configuration) if block_given?
16
+
17
+ require "natural_born_slugger/class_methods"
18
+ require "natural_born_slugger/instance_methods"
19
+
20
+ configuration
21
+ end
22
+
23
+ def self.logger
24
+ @logger ||= configuration.logger
25
+ end
26
+
27
+ def self.included(base)
28
+ base.extend ClassMethods
29
+ end
30
+ end
31
+
32
+ require "natural_born_slugger/extensions/string"
33
+ require "natural_born_slugger/version"
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'natural_born_slugger/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "natural_born_slugger"
8
+ spec.version = NaturalBornSlugger::VERSION
9
+ spec.authors = ["Christopher Keele"]
10
+ spec.email = ["dev@chriskeele.com"]
11
+ spec.description = %q{A gem for managing composed attributes, especially natural keys and url slugs.}
12
+ spec.summary = %q{Easily define automatically-updated composed attributes. Includes ORM helpers and a Rack-based URL redirector.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "yard", "~> 0.8"
24
+ spec.add_development_dependency "redcarpet", "~> 2.2"
25
+ spec.add_development_dependency "rspec", "~> 2.13"
26
+
27
+ spec.add_dependency 'activesupport', '~> 3.1'
28
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: natural_born_slugger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Keele
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redcarpet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.13'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '3.1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '3.1'
97
+ description: A gem for managing composed attributes, especially natural keys and url
98
+ slugs.
99
+ email:
100
+ - dev@chriskeele.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - .yardopts
107
+ - Gemfile
108
+ - LICENSE.md
109
+ - README.md
110
+ - Rakefile
111
+ - lib/natural_born_slugger.rb
112
+ - lib/natural_born_slugger/class_methods.rb
113
+ - lib/natural_born_slugger/configuration.rb
114
+ - lib/natural_born_slugger/exceptions.rb
115
+ - lib/natural_born_slugger/extensions/string.rb
116
+ - lib/natural_born_slugger/instance_methods.rb
117
+ - lib/natural_born_slugger/version.rb
118
+ - natural_born_slugger.gemspec
119
+ homepage: ''
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.0.0
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Easily define automatically-updated composed attributes. Includes ORM helpers
143
+ and a Rack-based URL redirector.
144
+ test_files: []
145
+ has_rdoc: