diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2014-01-11 17:28:43 -0800 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2014-01-11 17:28:43 -0800 |
commit | 11e8badb16876e6bd72c874631d25aec41dad293 (patch) | |
tree | 5ec87baa3a8e7652c46d6804b174b81b8f9c7745 /activerecord/lib/active_record/enum.rb | |
parent | 474ebc55bd13ad58626a49dfc44c8e6407813935 (diff) | |
parent | caa981d88112f019ade868f75af6b5f399c244a4 (diff) | |
download | rails-11e8badb16876e6bd72c874631d25aec41dad293.tar.gz rails-11e8badb16876e6bd72c874631d25aec41dad293.tar.bz2 rails-11e8badb16876e6bd72c874631d25aec41dad293.zip |
Merge branch 'master' into set_binds
* master: (2794 commits)
doc, API example on how to use `Model#exists?` with multiple IDs. [ci skip]
Restore DATABASE_URL even if it's nil in connection_handler test
[ci skip] - error_messages_for has been deprecated since 2.3.8 - lets reduce any confusion for users
Ensure Active Record connection consistency
Revert "ask the fixture set for the sql statements"
Check `respond_to` before delegation due to: https://github.com/ruby/ruby/commit/d781caaf313b8649948c107bba277e5ad7307314
Adding Hash#compact and Hash#compact! methods
MySQL version 4.1 was EOL on December 31, 2009 We should at least recommend modern versions of MySQL to users.
clear cache on body close so that cache remains during rendering
add a more restricted codepath for templates fixes #13390
refactor generator tests to use block form of Tempfile
Fix typo [ci skip]
Move finish_template as the last public method in the generator
Minor typos fix [ci skip]
make `change_column_null` reversible. Closes #13576.
create/drop test and development databases only if RAILS_ENV is nil
Revert "Speedup String#to"
typo fix in test name. [ci skip].
`core_ext/string/access.rb` test what we are documenting.
Fix typo in image_tag documentation
...
Conflicts:
activerecord/lib/active_record/associations/join_dependency/join_association.rb
activerecord/lib/active_record/relation/query_methods.rb
Diffstat (limited to 'activerecord/lib/active_record/enum.rb')
-rw-r--r-- | activerecord/lib/active_record/enum.rb | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb new file mode 100644 index 0000000000..9ad718a02a --- /dev/null +++ b/activerecord/lib/active_record/enum.rb @@ -0,0 +1,116 @@ +module ActiveRecord + # Declare an enum attribute where the values map to integers in the database, + # but can be queried by name. Example: + # + # class Conversation < ActiveRecord::Base + # enum status: [ :active, :archived ] + # end + # + # # conversation.update! status: 0 + # conversation.active! + # conversation.active? # => true + # conversation.status # => "active" + # + # # conversation.update! status: 1 + # conversation.archived! + # conversation.archived? # => true + # conversation.status # => "archived" + # + # # conversation.update! status: 1 + # conversation.status = "archived" + # + # # conversation.update! status: nil + # conversation.status = nil + # conversation.status.nil? # => true + # conversation.status # => nil + # + # Scopes based on the allowed values of the enum field will be provided + # as well. With the above example, it will create an +active+ and +archived+ + # scope. + # + # You can set the default value from the database declaration, like: + # + # create_table :conversations do |t| + # t.column :status, :integer, default: 0 + # end + # + # Good practice is to let the first declared status be the default. + # + # Finally, it's also possible to explicitly map the relation between attribute and + # database integer with a +Hash+: + # + # class Conversation < ActiveRecord::Base + # enum status: { active: 0, archived: 1 } + # end + # + # Note that when an +Array+ is used, the implicit mapping from the values to database + # integers is derived from the order the values appear in the array. In the example, + # <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt> + # is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the + # database. + # + # Therefore, once a value is added to the enum array, its position in the array must + # be maintained, and new values should only be added to the end of the array. To + # remove unused values, the explicit +Hash+ syntax should be used. + # + # In rare circumstances you might need to access the mapping directly. + # The mappings are exposed through a constant with the attributes name: + # + # Conversation::STATUS # => { "active" => 0, "archived" => 1 } + # + # Use that constant when you need to know the ordinal value of an enum: + # + # Conversation.where("status <> ?", Conversation::STATUS[:archived]) + module Enum + def enum(definitions) + klass = self + definitions.each do |name, values| + # STATUS = { } + enum_values = _enum_methods_module.const_set name.to_s.upcase, ActiveSupport::HashWithIndifferentAccess.new + name = name.to_sym + + _enum_methods_module.module_eval do + # def status=(value) self[:status] = STATUS[value] end + define_method("#{name}=") { |value| + if enum_values.has_key?(value) || value.blank? + self[name] = enum_values[value] + elsif enum_values.has_value?(value) + # Assigning a value directly is not a end-user feature, hence it's not documented. + # This is used internally to make building objects from the generated scopes work + # as expected, i.e. +Conversation.archived.build.archived?+ should be true. + self[name] = value + else + raise ArgumentError, "'#{value}' is not a valid #{name}" + end + } + + # def status() STATUS.key self[:status] end + define_method(name) { enum_values.key self[name] } + + pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index + pairs.each do |value, i| + enum_values[value] = i + + # scope :active, -> { where status: 0 } + klass.scope value, -> { klass.where name => i } + + # def active?() status == 0 end + define_method("#{value}?") { self[name] == i } + + # def active!() update! status: :active end + define_method("#{value}!") { update! name => value } + end + end + end + end + + private + def _enum_methods_module + @_enum_methods_module ||= begin + mod = Module.new + include mod + mod + end + end + end +end |