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.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in constant-enum.gemspec
4
+ gemspec
@@ -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.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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,2 @@
1
+ require 'constant_enum/base'
2
+ require 'constant_enum/version'
@@ -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
@@ -0,0 +1,3 @@
1
+ module ConstantEnum
2
+ VERSION = "0.1.0"
3
+ 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: []