constant-enum 0.1.0

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