galetahub-enum_field 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +127 -0
- data/Rakefile +44 -0
- data/lib/enum_field.rb +22 -0
- data/lib/enum_field/builder.rb +78 -0
- data/lib/enum_field/define_enum.rb +17 -0
- data/lib/enum_field/enumerated_attribute.rb +88 -0
- data/lib/enum_field/railtie.rb +13 -0
- data/lib/enum_field/version.rb +3 -0
- metadata +75 -0
data/README.rdoc
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
= enum_field
|
2
|
+
|
3
|
+
* http://github.com/paraseba/enum_field
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Enables Active Record attributes to point to enum like objects, by saving in your database
|
8
|
+
only an integer ID.
|
9
|
+
|
10
|
+
|
11
|
+
== FEATURES:
|
12
|
+
|
13
|
+
* Allows creation of Classes with enum like behaviour.
|
14
|
+
* Allows any number of members and methods in the enum classes.
|
15
|
+
* Allows an integer id to be used in your database columns to link to the enum members (user.role_id)
|
16
|
+
* Enables higher abstraction interaction with +AR+ attributes:
|
17
|
+
* <code>user.role = Role.admin</code>
|
18
|
+
* <code>if user.role.can_edit?</code>
|
19
|
+
* Saves in your +AR+ tables, only an integer id pointing to the enumeration member.
|
20
|
+
|
21
|
+
== SYNOPSIS:
|
22
|
+
|
23
|
+
When in an Active Record class, you have an attribute like role, state or country you have
|
24
|
+
several options.
|
25
|
+
|
26
|
+
* You can create a roles, states or countries table, and dump there all possible values.
|
27
|
+
* You can use a string to identify, for instance, the role.
|
28
|
+
* You can use an id to identify the role.
|
29
|
+
|
30
|
+
If you are not confortable with any of this options, maybe +enum_field+ is an answer for you.
|
31
|
+
|
32
|
+
== BASIC USAGE:
|
33
|
+
|
34
|
+
class Role
|
35
|
+
define_enum do |builder|
|
36
|
+
builder.member :admin
|
37
|
+
builder.member :manager
|
38
|
+
builder.member :employee
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
# in the database table there is a role_id integer column
|
44
|
+
enumerated_attribute :role
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
link_to_if(current_user.role == Role.admin, edit_foo_path(@foo))
|
49
|
+
|
50
|
+
user.role = Role.manager
|
51
|
+
user.role_id == Role.manager.id #will be true
|
52
|
+
|
53
|
+
User.first.role.id == User.first.role_id #will be true
|
54
|
+
|
55
|
+
Your enum classes can have all the methods you need:
|
56
|
+
|
57
|
+
class PhoneType
|
58
|
+
def initialize(name)
|
59
|
+
@name = name
|
60
|
+
end
|
61
|
+
attr_reader :name
|
62
|
+
|
63
|
+
define_enum do |b|
|
64
|
+
b.member :home, :object => PhoneType.new('home')
|
65
|
+
b.member :commercial, :object => PhoneType.new('commercial')
|
66
|
+
b.member :mobile, :object => PhoneType.new('mobile')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
user.phone.type.name
|
71
|
+
|
72
|
+
You have some +AR+ like methods in enum classes
|
73
|
+
|
74
|
+
PhoneType.all == [PhoneType.home, PhoneType.commercial, PhoneType.mobile] # ordered all
|
75
|
+
PhoneType.first == PhoneType.home
|
76
|
+
PhoneType.last == PhoneType.mobile
|
77
|
+
|
78
|
+
PhoneType.find_by_id(PhoneType.home.id) == PhoneType.home
|
79
|
+
PhoneType.find_by_id(123456) == nil
|
80
|
+
PhoneType.find(2) == PhoneType.commercial
|
81
|
+
PhoneType.find(123456) # will raise
|
82
|
+
|
83
|
+
The library also mimics has_many :through behavior, for cases such as:
|
84
|
+
|
85
|
+
class Role
|
86
|
+
define_enum do |builder|
|
87
|
+
builder.member :admin
|
88
|
+
builder.member :manager
|
89
|
+
builder.member :employee
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class User
|
94
|
+
has_many_enumerated_attributes :roles, :through => UserRole
|
95
|
+
end
|
96
|
+
|
97
|
+
class UserRole < ActiveRecord::Base
|
98
|
+
belongs_to :user
|
99
|
+
enumerated_attribute :role
|
100
|
+
end
|
101
|
+
|
102
|
+
user = User.create
|
103
|
+
user.role = [Role.manager, Role.admin]
|
104
|
+
user.roles.include?(Role.admin) #will be true
|
105
|
+
user.roles.include?(Role.manager) #will be true
|
106
|
+
user.roles.include?(Role.employee) #will be false
|
107
|
+
user.role_ids.include?(Role.manager.id) #will be true
|
108
|
+
user.role_ids = [Role.employee.id]
|
109
|
+
user.roles.include?(Role.employee) #will be true
|
110
|
+
user.roles.include?(Role.admin) #will be false
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
== REQUIREMENTS:
|
115
|
+
|
116
|
+
* activerecord
|
117
|
+
|
118
|
+
|
119
|
+
== INSTALL:
|
120
|
+
|
121
|
+
rails plugin install git://github.com/galetahub/enum_field.git
|
122
|
+
|
123
|
+
== LICENSE:
|
124
|
+
|
125
|
+
(The MIT License)
|
126
|
+
|
127
|
+
Copyright (c) 2009 Sebastián Bernardo Galkin
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require File.join(File.dirname(__FILE__), 'lib', 'enum_field', 'version')
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Test the enum_field plugin.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the enum_field plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'EnumField'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README.rdoc')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'jeweler'
|
29
|
+
Jeweler::Tasks.new do |s|
|
30
|
+
s.name = "galetahub-enum_field"
|
31
|
+
s.version = EnumField::VERSION
|
32
|
+
s.summary = "Enumerated attributes"
|
33
|
+
s.description = "Enables Active Record attributes to point to enum like objects, by saving in your database only an integer ID"
|
34
|
+
s.email = "galeta.igor@gmail.com"
|
35
|
+
s.homepage = "https://github.com/galetahub/enum_field"
|
36
|
+
s.authors = ["Igor Galeta", "Pavlo Galeta"]
|
37
|
+
s.files = FileList["[A-Z]*", "lib/**/*"]
|
38
|
+
s.extra_rdoc_files = FileList["[A-Z]*"] - %w(Rakefile)
|
39
|
+
end
|
40
|
+
|
41
|
+
Jeweler::GemcutterTasks.new
|
42
|
+
rescue LoadError
|
43
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
44
|
+
end
|
data/lib/enum_field.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
autoload :DefineEnum, 'enum_field/define_enum'
|
4
|
+
autoload :Builder, 'enum_field/builder'
|
5
|
+
autoload :EnumeratedAttribute, 'enum_field/enumerated_attribute'
|
6
|
+
autoload :Version, 'enum_field/version'
|
7
|
+
|
8
|
+
class BadId < StandardError
|
9
|
+
def initialize(repeated_id)
|
10
|
+
@repeated_id = repeated_id
|
11
|
+
end
|
12
|
+
attr_reader :repeated_id
|
13
|
+
end
|
14
|
+
|
15
|
+
class RepeatedId < StandardError; end
|
16
|
+
class InvalidId < StandardError; end
|
17
|
+
class InvalidOptions < StandardError; end
|
18
|
+
class ObjectNotFound < StandardError; end
|
19
|
+
end
|
20
|
+
|
21
|
+
Module.send(:include, EnumField::DefineEnum)
|
22
|
+
require 'enum_field/railtie'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
class Builder
|
4
|
+
def initialize(target)
|
5
|
+
@target = target
|
6
|
+
@next_id = 0
|
7
|
+
@id2obj = {}
|
8
|
+
@name2obj = {}
|
9
|
+
@sorted = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def member(name, options = {})
|
13
|
+
obj, candidate_id = process_options(options)
|
14
|
+
assign_id(obj, candidate_id)
|
15
|
+
define_in_meta(name) { obj }
|
16
|
+
save(name, obj)
|
17
|
+
obj.freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def all
|
21
|
+
@sorted.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
def names
|
25
|
+
@name2obj.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def find(id)
|
29
|
+
find_by_id(id) or raise EnumField::ObjectNotFound
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_by_id(id)
|
33
|
+
@id2obj[id.to_i]
|
34
|
+
end
|
35
|
+
|
36
|
+
def first; @sorted.first; end
|
37
|
+
def last; @sorted.last; end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def define_in_meta(name, &block)
|
42
|
+
metaclass = class << @target; self; end
|
43
|
+
metaclass.send(:define_method, name, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def assign_id(obj, candidate_id)
|
47
|
+
id = new_id(candidate_id)
|
48
|
+
obj.class.send(:attr_reader, :id)
|
49
|
+
obj.instance_variable_set(:@id, id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_id(candidate)
|
53
|
+
validate_candidate_id(candidate)
|
54
|
+
candidate || find_next_id
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_candidate_id(id)
|
58
|
+
raise EnumField::InvalidId.new(id) unless id.nil? || id.is_a?(Integer) && id > 0
|
59
|
+
raise EnumField::RepeatedId.new(id) if @id2obj.has_key?(id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_next_id
|
63
|
+
@next_id += 1 while @id2obj.has_key?(@next_id) || @next_id <= 0
|
64
|
+
@next_id
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_options(options)
|
68
|
+
raise EnumField::InvalidOptions unless options.reject {|k,v| k == :object || k == :id}.empty?
|
69
|
+
[options[:object] || @target.new, options[:id]]
|
70
|
+
end
|
71
|
+
|
72
|
+
def save(name, obj)
|
73
|
+
@id2obj[obj.id] = obj
|
74
|
+
@sorted << obj
|
75
|
+
@name2obj[name] = obj
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
module DefineEnum
|
4
|
+
def define_enum(&block)
|
5
|
+
@enum_builder ||= EnumField::Builder.new(self)
|
6
|
+
yield @enum_builder
|
7
|
+
|
8
|
+
[:all, :names, :find_by_id, :find, :first, :last].each do |method|
|
9
|
+
instance_eval <<-END
|
10
|
+
def #{method}(*args, &block)
|
11
|
+
@enum_builder.send(:#{method}, *args, &block)
|
12
|
+
end
|
13
|
+
END
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
# Easies the inclusion of enumerations as ActiveRecord columns.
|
4
|
+
# If you have a User AR, with a role_id column, you could do:
|
5
|
+
# <tt>
|
6
|
+
# class User
|
7
|
+
# enumerated_attribute :role
|
8
|
+
# end
|
9
|
+
# </tt>
|
10
|
+
#
|
11
|
+
# This assumes a Role class, and will define role and role= methods in User class.
|
12
|
+
# These added methods expect an object of class Role, and Role should provide a find class method.
|
13
|
+
# You could get this by using Enumerated mixin
|
14
|
+
#
|
15
|
+
# Similar to has_many :through in AR, it creates reader and writter methods to get enumerated atributes going through an
|
16
|
+
# intermediate model.
|
17
|
+
# For instance:
|
18
|
+
# <tt>
|
19
|
+
# class User
|
20
|
+
# has_many_enumerated_attributes :roles, :through => UserRole
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class UserRole < ActiveRecord::Base
|
24
|
+
# belongs_to :user
|
25
|
+
# enumerated_attribute :role
|
26
|
+
# end
|
27
|
+
# </tt>
|
28
|
+
# This assumes a Role class, the UserRole AR class is there just to persist the one-to-many relationship.
|
29
|
+
# This will define roles, roles=, role_ids and role_ids= methods in User class
|
30
|
+
|
31
|
+
module EnumeratedAttribute
|
32
|
+
# Define an enumerated field of the AR class
|
33
|
+
# * +name_attribute+: the name of the field that will be added, for instance +role+
|
34
|
+
# * +options+: Valid options are:
|
35
|
+
# * +id_attribute+: the name of the AR column where the enumerated id will be save. Defaults to
|
36
|
+
# +name_attribute+ with an +_id+ suffix.
|
37
|
+
# * +class+: the class that will be instantiated when +name_attribute+ method is called. Defaults to
|
38
|
+
# +name_attribute+ in camelcase form.
|
39
|
+
def enumerated_attribute(name_attribute, options = {})
|
40
|
+
id_attribute = options[:id_attribute] || (name_attribute.to_s + '_id').to_sym
|
41
|
+
klass = options[:class] || name_attribute.to_s.camelcase.constantize
|
42
|
+
define_method(name_attribute) do
|
43
|
+
(raw = read_attribute(id_attribute)) && klass.find_by_id(raw)
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method(name_attribute.to_s + '=') do |value|
|
47
|
+
write_attribute(id_attribute, value ? value.id : nil)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# alias of enumerated_attribute
|
52
|
+
alias belongs_to_enumerated_attribute enumerated_attribute
|
53
|
+
|
54
|
+
# Defines a one-to-many association between an AR class and the enumerated
|
55
|
+
# * +association+: the name of the one-to-many association, for instance +roles+
|
56
|
+
# * +options+: Valid options are:
|
57
|
+
# * +through+ : the name of the AR class needed to persist the one-to-many association.
|
58
|
+
# Defaults to AR class in camelcase form concatenated with the enumerated class in camelcase form.
|
59
|
+
# * +class+: the enumerated class, it will be instantiated +n+ times when +association+ method is called.
|
60
|
+
# Defaults to +association+ in singular camelcase form.
|
61
|
+
def has_many_enumerated_attributes(association, options = {})
|
62
|
+
enum_attr = association.to_s.singularize
|
63
|
+
klass = options[:class] || enum_attr.camelcase.constantize
|
64
|
+
through = options[:through] || (self.name + klass.name)
|
65
|
+
self_attribute = self.name.demodulize.underscore
|
66
|
+
association_ids = association.to_s.singularize + '_ids'
|
67
|
+
has_many_aux = through.demodulize.underscore.pluralize
|
68
|
+
|
69
|
+
has_many has_many_aux, {:class_name => through, :dependent => :destroy}
|
70
|
+
|
71
|
+
define_method(association) do
|
72
|
+
self.send(has_many_aux).map(&enum_attr.to_sym)
|
73
|
+
end
|
74
|
+
|
75
|
+
define_method(association.to_s + '=') do |values|
|
76
|
+
self.send(has_many_aux + '=', values.map{|g| through.constantize.new(self_attribute => self, enum_attr => g)})
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method(association_ids) do
|
80
|
+
self.send(association).map(&:id)
|
81
|
+
end
|
82
|
+
|
83
|
+
define_method(association_ids + '=') do |values|
|
84
|
+
self.send(has_many_aux + '=', values.map{|g| g.to_i unless g.blank?}.compact.map{|g_id| through.constantize.new(self_attribute => self, enum_attr + '_id' => g_id) })
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rails'
|
3
|
+
require 'enum_field'
|
4
|
+
|
5
|
+
module EnumField
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
config.before_initialize do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
ActiveRecord::Base.send(:extend, EnumField::EnumeratedAttribute)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: galetahub-enum_field
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Igor Galeta
|
14
|
+
- Pavlo Galeta
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-06-16 00:00:00 +03:00
|
20
|
+
default_executable:
|
21
|
+
dependencies: []
|
22
|
+
|
23
|
+
description: Enables Active Record attributes to point to enum like objects, by saving in your database only an integer ID
|
24
|
+
email: galeta.igor@gmail.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- README.rdoc
|
33
|
+
- Rakefile
|
34
|
+
- lib/enum_field.rb
|
35
|
+
- lib/enum_field/builder.rb
|
36
|
+
- lib/enum_field/define_enum.rb
|
37
|
+
- lib/enum_field/enumerated_attribute.rb
|
38
|
+
- lib/enum_field/railtie.rb
|
39
|
+
- lib/enum_field/version.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: https://github.com/galetahub/enum_field
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.6.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Enumerated attributes
|
74
|
+
test_files: []
|
75
|
+
|