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 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
@@ -0,0 +1,3 @@
1
+ module EnumField
2
+ VERSION = "0.1.4".freeze
3
+ 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
+