constant-enum 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|