constant-enum 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +102 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/constant-enum.gemspec +25 -0
- data/lib/constant-enum.rb +1 -0
- data/lib/constant_enum.rb +2 -0
- data/lib/constant_enum/base.rb +190 -0
- data/lib/constant_enum/version.rb +3 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 759b1b09e07e81784485573cadc9485f804f7b8b
|
4
|
+
data.tar.gz: 62fde98228b95a70b876ad8a06ca7f13ed077b0c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7193c5713e5644d3d21389fbf8b53c4ac7506c6e5e51b3451bf837ee48acca71427b478989ab333dd09f6d294deb1049b0a8333ebffdb3602497b5ce17bde82b
|
7
|
+
data.tar.gz: 4bc596e832968f4e168d677cffc6c09ca685e3a9afb004baaf0f6089f80076ec2e09fb1160634d299b513c8cdb8e89495fd903d85ae63ec46ecef7c54d98fbe9
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Constant::Enum
|
2
|
+
|
3
|
+
ActiveRecord-like model for dealing with constant data. Works well with
|
4
|
+
[ActiveRecord 4.x enums](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html).
|
5
|
+
In their basic form, ActiveRecord enums are very simplistic, so this gem seeks to
|
6
|
+
add more robust functionality.
|
7
|
+
|
8
|
+
This does not have any dependencies and could be used standalone, with Sinatra, etc.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
First, create a class that inherits from `ConstantEnum::Base`, similar to how you would
|
13
|
+
create an `ActiveRecord::Base` class. This will hold your enum names and IDs.
|
14
|
+
|
15
|
+
In the simplest structure, you specify a name and an integer ID. Imagine we're creating
|
16
|
+
an action sports blog that will cover certain genres, which we know ahead of time:
|
17
|
+
|
18
|
+
class Genre < ConstantEnum::Base
|
19
|
+
enum_of skate: 1,
|
20
|
+
surf: 2,
|
21
|
+
snow: 3,
|
22
|
+
bike: 4
|
23
|
+
end
|
24
|
+
|
25
|
+
Then, in any ActiveRecord class that wants to use this enum, you specify this enum class:
|
26
|
+
|
27
|
+
class Video < ActiveRecord::Base
|
28
|
+
enum genre: Genre.enum
|
29
|
+
|
30
|
+
# other code...
|
31
|
+
end
|
32
|
+
|
33
|
+
Once setup, you can now do things like:
|
34
|
+
|
35
|
+
@genre = Genre.find_by_name!(:skate)
|
36
|
+
@videos = Video.where(genre: @genre)
|
37
|
+
|
38
|
+
For convenience, the class method `[]` will return the ID from the name, enabling a
|
39
|
+
shortcut calling form:
|
40
|
+
|
41
|
+
@videos = Video.where(genre: Genre[:skate])
|
42
|
+
|
43
|
+
In addition to translating names to IDs, several helper methods are provided to make
|
44
|
+
day-to-day life easier:
|
45
|
+
|
46
|
+
@genre = Genre.find_by_name!(:skate)
|
47
|
+
@genre.name # :skate
|
48
|
+
@genre.slug # "skate"
|
49
|
+
@genre.title # "Skate" - requires Rails (ActiveSupport Inflector)
|
50
|
+
|
51
|
+
You can create interesting route URLs by using these in Rails `config/routes.rb`:
|
52
|
+
|
53
|
+
# /videos/bike, /videos/surf, etc
|
54
|
+
get 'videos/:genre' => 'videos#index', as: 'videos_genre',
|
55
|
+
constraints: { genre: Genre.all.map(&:slug) }
|
56
|
+
|
57
|
+
Then using the shortcut form show above, you can (safely) do a query with this param:
|
58
|
+
|
59
|
+
@videos = Video.where(genre: Genre[ params[:genre] ])
|
60
|
+
|
61
|
+
This will raise `ConstantEnum::RecordNotFound`, which you can catch in your controllers to provide a clean error:
|
62
|
+
|
63
|
+
rescue_from ActiveRecord::RecordNotFound, ConstantEnum::RecordNotFound do
|
64
|
+
# show error page
|
65
|
+
end
|
66
|
+
|
67
|
+
When building forms, you can use the special method `dropdown`, which provides options in the
|
68
|
+
same order that Rails form helpers expect them:
|
69
|
+
|
70
|
+
<%= f.input :genre, label: 'Genre', collection: Genre.dropdown %>
|
71
|
+
|
72
|
+
This will create a nice human-readable select list with the correct names and values.
|
73
|
+
|
74
|
+
Finally, if you have extra data for your enum, you can instead specify a hash:
|
75
|
+
|
76
|
+
class AssetType < ConstantEnum::Base
|
77
|
+
enum_of photo: {id: 1, type: 'jpg', bucket: 'photos'},
|
78
|
+
video: {id: 2, type: 'mp4', bucket: 'videos'}
|
79
|
+
|
80
|
+
# other code...
|
81
|
+
end
|
82
|
+
|
83
|
+
Then, retrieving a constant "record" will give you these attributes:
|
84
|
+
|
85
|
+
@at = AssetType.find_by_type!('jpg')
|
86
|
+
@at.id # 1
|
87
|
+
@at.bucket # 'photos'
|
88
|
+
|
89
|
+
You can even use `where` to return a list:
|
90
|
+
|
91
|
+
@ats = AssetType.where(type: 'jpg')
|
92
|
+
@ats = AssetType.where(bucket: 'videos')
|
93
|
+
|
94
|
+
And so on.
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nateware/constant-enum
|
99
|
+
|
100
|
+
## Copyright
|
101
|
+
|
102
|
+
Copyright (c) [Nate Wiger](http://github.com/nateware). All Rights Reserved. Released under the MIT License.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "constant_enum"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'constant_enum/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "constant-enum"
|
8
|
+
spec.version = ConstantEnum::VERSION
|
9
|
+
spec.authors = ["Nate Wiger"]
|
10
|
+
spec.email = ["nwiger@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{ActiveRecord-like model for constant data.}
|
13
|
+
spec.description = %q{ActiveRecord-like model for constant data. Designed to work well with ActiveRecord enums.}
|
14
|
+
spec.homepage = "https://github.com/nateware/constant-enum"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
spec.add_development_dependency "activesupport"
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'constant_enum'
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module ConstantEnum
|
2
|
+
class RecordNotFound < StandardError; end
|
3
|
+
#
|
4
|
+
# This supports the creation of a constant class that is designed to work
|
5
|
+
# well with ActiveRecord enums. The benefit is you get additional functionality,
|
6
|
+
# like finders and slugs. Performance is excellent as these are in-memory
|
7
|
+
# structures and not DB calls.
|
8
|
+
#
|
9
|
+
# In the simplest structure, you specify a name and an integer ID:
|
10
|
+
#
|
11
|
+
# class Genre < ConstantEnum::Base
|
12
|
+
# enum_of skate: 1,
|
13
|
+
# surf: 2,
|
14
|
+
# snow: 3,
|
15
|
+
# bike: 4,
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Then, in an ActiveRecord class that wants to use this enum:
|
19
|
+
#
|
20
|
+
# class Video < ActiveRecord::Base
|
21
|
+
# enum genre: Genre.enum
|
22
|
+
# ...
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# From there, you can now do things like:
|
26
|
+
#
|
27
|
+
# @genre = Genre.find_by_name!(:skate)
|
28
|
+
# @videos = Video.where(genre: genre)
|
29
|
+
#
|
30
|
+
# Interesting routes can be created like:
|
31
|
+
#
|
32
|
+
# # /videos/bike, /videos/surf, etc
|
33
|
+
# get 'videos/:genre' => 'videos#index', as: 'videos_genre',
|
34
|
+
# constraints: { genre: Genre.all.map(&:slug) }
|
35
|
+
#
|
36
|
+
# If you have extra data for your enum, you can specify a hash:
|
37
|
+
#
|
38
|
+
# class AssetType < ConstantEnum::Base
|
39
|
+
# enum_of \
|
40
|
+
# photo: {id: 1, type: 'jpg', bucket: 'photos'},
|
41
|
+
# video: {id: 2, type: 'mp4', bucket: 'videos'}
|
42
|
+
#
|
43
|
+
class Base < Struct.new(:name, :id, :attributes)
|
44
|
+
extend Enumerable
|
45
|
+
|
46
|
+
def self.enum_of(hash)
|
47
|
+
raise ArgumentError, "#{self}.enum_of name1: id2, name2: id2" unless hash.is_a?(Hash)
|
48
|
+
@data = {}
|
49
|
+
@enum = {}
|
50
|
+
hash.each do |name,value|
|
51
|
+
if value.is_a?(Hash)
|
52
|
+
@enum[name] = value[:id] # for Rails
|
53
|
+
@data[name] = new(name, value[:id], value)
|
54
|
+
else
|
55
|
+
@enum[name] = value # for Rails
|
56
|
+
@data[name] = new(name, value)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create constants such as ADMIN=1 etc
|
60
|
+
const_name =
|
61
|
+
name.to_s.upcase.strip.gsub(/[-\s]+/,'_').sub(/^[0-9_]+/,'').gsub(/\W+/,'')
|
62
|
+
const_set const_name, @enum[name] unless const_defined?(const_name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Just return the hash. For use in ActiveRecord models, eg "enum role: Role.enum"
|
67
|
+
def self.enum
|
68
|
+
@enum
|
69
|
+
end
|
70
|
+
|
71
|
+
# Role[:admin] => 1 ; also Role[1] => 1 so models don't have to care.
|
72
|
+
def self.[](what)
|
73
|
+
if what.is_a?(Integer)
|
74
|
+
find(what).id
|
75
|
+
else
|
76
|
+
find_by_name!(what).id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.find_by_name!(name)
|
81
|
+
find_by!(name: name)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.find_by_name(name)
|
85
|
+
find_by_name!(name) rescue nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.find_by_slug!(slug)
|
89
|
+
find_by!(slug: slug)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.find_by_slug(slug)
|
93
|
+
find_by!(slug: slug) rescue nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.find(id)
|
97
|
+
find_by!(id: id)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.find_by_id(id)
|
101
|
+
find(id) rescue nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.find_by(hash)
|
105
|
+
where(hash).first
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.find_by!(hash)
|
109
|
+
find_by(hash) or
|
110
|
+
raise RecordNotFound,
|
111
|
+
%Q(Couldn't find #{self} with #{hash.collect{|k,v| "#{k}=#{v.inspect}"} * ' '})
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.all
|
115
|
+
where()
|
116
|
+
end
|
117
|
+
|
118
|
+
# Allow simple detection, similar to ActiveRecord. This method is a little
|
119
|
+
# verbose because we need to mimic where({}) which returns everything.
|
120
|
+
# It also supports where(type: 'video', active: true) for multiple restrictions.
|
121
|
+
def self.where(hash={})
|
122
|
+
raise RecordNotFound, "No records defined for #{self}" if @data.nil?
|
123
|
+
results = []
|
124
|
+
@data.each do |name,struct|
|
125
|
+
found = true # where({})
|
126
|
+
hash.each do |k,v|
|
127
|
+
if k.to_s == 'name'
|
128
|
+
found = false if name.to_s != v.to_s
|
129
|
+
else
|
130
|
+
found = false if struct.send(k) != v
|
131
|
+
end
|
132
|
+
end
|
133
|
+
# for where({})
|
134
|
+
results << struct if found
|
135
|
+
end
|
136
|
+
results
|
137
|
+
end
|
138
|
+
|
139
|
+
# Enumerable support
|
140
|
+
def self.each(&block)
|
141
|
+
all.each(&block)
|
142
|
+
end
|
143
|
+
singleton_class.send :alias_method, :find_each, :each
|
144
|
+
|
145
|
+
def self.ids
|
146
|
+
enum.map{|r| r.last}
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.names
|
150
|
+
enum.map{|r| r.first}
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.count
|
154
|
+
enum.keys.length
|
155
|
+
end
|
156
|
+
|
157
|
+
# Dropdown is actually [Title, name] for Rails 4.1 enums
|
158
|
+
def self.dropdown
|
159
|
+
enum.collect{|name,id| [name.to_s.titleize, name] }
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Instance methods: @role = Role.new ; @role.slug
|
164
|
+
#
|
165
|
+
def slug
|
166
|
+
name.to_s.downcase.gsub(/\W+/,'')
|
167
|
+
end
|
168
|
+
|
169
|
+
def title
|
170
|
+
name.to_s.titleize
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
name.to_s
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_param
|
178
|
+
id.to_s
|
179
|
+
end
|
180
|
+
|
181
|
+
# Handle extra attribute methods like .label or .delivery_type
|
182
|
+
def method_missing(meth, *args, &block)
|
183
|
+
if attributes.has_key?(meth)
|
184
|
+
attributes[meth]
|
185
|
+
else
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: constant-enum
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nate Wiger
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-20 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.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
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: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: ActiveRecord-like model for constant data. Designed to work well with
|
70
|
+
ActiveRecord enums.
|
71
|
+
email:
|
72
|
+
- nwiger@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".travis.yml"
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- constant-enum.gemspec
|
85
|
+
- lib/constant-enum.rb
|
86
|
+
- lib/constant_enum.rb
|
87
|
+
- lib/constant_enum/base.rb
|
88
|
+
- lib/constant_enum/version.rb
|
89
|
+
homepage: https://github.com/nateware/constant-enum
|
90
|
+
licenses: []
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.4.5
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: ActiveRecord-like model for constant data.
|
112
|
+
test_files: []
|