├── .github └── workflows │ └── test.yml ├── .gitignore ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── MIGRATE.md ├── README.md ├── Rakefile ├── enumerations.gemspec ├── lib ├── enumerations.rb └── enumerations │ ├── base.rb │ ├── configuration.rb │ ├── errors.rb │ ├── finder_methods.rb │ ├── reflection.rb │ ├── value.rb │ └── version.rb └── test ├── base_test.rb ├── configuration ├── configuration.rb ├── custom_configuration_test.rb ├── enumerations_test.rb └── finder_test.rb ├── configuration_test.rb ├── enumerations_test.rb ├── finder_test.rb ├── helpers ├── database_helper.rb ├── locale_helper.rb └── test_helper.rb ├── locales └── hr.yml ├── reflection_test.rb ├── translation_test.rb └── value_test.rb /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test suite 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | name: Ruby ${{ matrix.ruby }} 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | ruby: [2.6, 2.7, '3.0', 3.1] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby }} 22 | bundler-cache: true 23 | - name: Run tests 24 | run: bundle exec rake test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | Gemfile.lock 4 | .todo 5 | coverage -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | LineLength: 2 | Max: 100 3 | 4 | Documentation: 5 | Enabled: False 6 | 7 | Style/SymbolArray: 8 | Enabled: False 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v2.6.0](https://github.com/infinum/enumerations/tree/v2.6.0) (2024-10-22) 4 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.5.4...v2.6.0) 5 | 6 | **Implemented enhancements:** 7 | 8 | - Add ability to specify foreign key suffix on enum class. 9 | 10 | ## [v2.5.4](https://github.com/infinum/enumerations/tree/v2.5.4) (2023-01-09) 11 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.5.3...v2.5.4) 12 | 13 | **Implemented enhancements:** 14 | 15 | - Add MIT license file. 16 | 17 | ## [v2.5.3](https://github.com/infinum/enumerations/tree/v2.5.3) (2022-02-02) 18 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.5.2...v2.5.3) 19 | 20 | **Fixed bugs:** 21 | 22 | - Prevent `method_missing` instantiates new instance of `Enumerations::Base`; returns `String` instead 23 | 24 | **Merged pull requests:** 25 | 26 | - Prevent method_missing to instantiates new Enumerations::Base [\#58](https://github.com/infinum/enumerations/pull/58) ([PetarCurkovic](https://github.com/PetarCurkovic)) 27 | 28 | ## [v2.5.2](https://github.com/infinum/enumerations/tree/v2.5.2) (2022-01-27) 29 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.5.1...v2.5.2) 30 | 31 | **Implemented enhancements:** 32 | 33 | - Adds Ruby 3.1 to test suite. Changes the runner to ubuntu-latest. 34 | 35 | **Closed issues:** 36 | 37 | - Ruby 3.0, `respond_to? :name` always returns true [\#55](https://github.com/infinum/enumerations/issues/55) 38 | 39 | **Merged pull requests:** 40 | 41 | - Initialize `ActiveSupport::Multibyte::Chars` with a String [\#56](https://github.com/infinum/enumerations/pull/56) ([lovro-bikic](https://github.com/lovro-bikic)) 42 | - Test against Ruby 3.1 [\#57](https://github.com/infinum/enumerations/pull/57) ([lovro-bikic](https://github.com/lovro-bikic)) 43 | 44 | ## [v2.5.1](https://github.com/infinum/enumerations/tree/v2.5.1) (2021-05-24) 45 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.5.0...v2.5.1) 46 | 47 | **Implemented enhancements:** 48 | 49 | - Add support for Ruby 3 [\#52](https://github.com/infinum/enumerations/issues/52) 50 | 51 | ## [v2.5.0](https://github.com/infinum/enumerations/tree/v2.5.0) (2021-03-03) 52 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.4.0...v2.5.0) 53 | 54 | **Implemented enhancements:** 55 | 56 | - Add `raise_invalid_value_error` configuration option to disable raising errors on invalid values 57 | 58 | ## [v2.4.0](https://github.com/infinum/enumerations/tree/v2.4.0) (2019-02-07) 59 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.3.3...v2.4.0) 60 | 61 | **Implemented enhancements:** 62 | 63 | - Add `translate_attributes` configuration option to disable translation of attributes 64 | 65 | ## [v2.3.3](https://github.com/infinum/enumerations/tree/v2.3.1) (2019-19-09) 66 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.3.2...v2.3.3) 67 | 68 | ## [v2.3.2](https://github.com/infinum/enumerations/tree/v2.3.1) (2019-03-27) 69 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.3.1...v2.3.2) 70 | 71 | ## [v2.3.1](https://github.com/infinum/enumerations/tree/v2.3.1) (2017-09-08) 72 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.3.0...v2.3.1) 73 | 74 | ## [v2.3.0](https://github.com/infinum/enumerations/tree/v2.3.0) (2017-09-08) 75 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.2.3...v2.3.0) 76 | 77 | **Implemented enhancements:** 78 | 79 | - Setting attribute to nil if enumeration value is unknown - surprising? [\#35](https://github.com/infinum/enumerations/issues/35) 80 | - Add `without` methods [\#31](https://github.com/infinum/enumerations/issues/31) 81 | - Add methods with '?' [\#19](https://github.com/infinum/enumerations/issues/19) 82 | - Add with\_enumeration\(enumeration\) scope [\#18](https://github.com/infinum/enumerations/issues/18) 83 | 84 | **Closed issues:** 85 | 86 | - Not assigning the enumeration when tweaking the primary and foreign key [\#32](https://github.com/infinum/enumerations/issues/32) 87 | - Undocumented feature - can't set attribute if it's not in the enumeration [\#30](https://github.com/infinum/enumerations/issues/30) 88 | 89 | **Merged pull requests:** 90 | 91 | - Feature/raising error on invalid enumeration value [\#38](https://github.com/infinum/enumerations/pull/38) ([PetarCurkovic](https://github.com/PetarCurkovic)) 92 | - Bugfix/config options [\#37](https://github.com/infinum/enumerations/pull/37) ([PetarCurkovic](https://github.com/PetarCurkovic)) 93 | - Feature/predicate methods for attributes [\#36](https://github.com/infinum/enumerations/pull/36) ([PetarCurkovic](https://github.com/PetarCurkovic)) 94 | - Feature/without scopes [\#34](https://github.com/infinum/enumerations/pull/34) ([PetarCurkovic](https://github.com/PetarCurkovic)) 95 | - Feature/with enumeration scope [\#33](https://github.com/infinum/enumerations/pull/33) ([PetarCurkovic](https://github.com/PetarCurkovic)) 96 | 97 | ## [v2.2.3](https://github.com/infinum/enumerations/tree/v2.2.3) (2017-03-17) 98 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.2.2...v2.2.3) 99 | 100 | ## [v2.2.2](https://github.com/infinum/enumerations/tree/v2.2.2) (2017-03-01) 101 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.2.1...v2.2.2) 102 | 103 | **Implemented enhancements:** 104 | 105 | - Add id and quoted\_id methods [\#20](https://github.com/infinum/enumerations/issues/20) 106 | 107 | **Fixed bugs:** 108 | 109 | - Error when assiging nil [\#21](https://github.com/infinum/enumerations/issues/21) 110 | 111 | **Closed issues:** 112 | 113 | - upgrade to new codeclimate reporter [\#25](https://github.com/infinum/enumerations/issues/25) 114 | - Giving a invalid enum name raises an error [\#23](https://github.com/infinum/enumerations/issues/23) 115 | - Make second argument to Enumerations.value optional [\#22](https://github.com/infinum/enumerations/issues/22) 116 | 117 | **Merged pull requests:** 118 | 119 | - Make second argument to Enumerations.value optional [\#28](https://github.com/infinum/enumerations/pull/28) ([domagojnakic](https://github.com/domagojnakic)) 120 | - Fix giving invalid enum name raises error [\#27](https://github.com/infinum/enumerations/pull/27) ([domagojnakic](https://github.com/domagojnakic)) 121 | - Replaced CodeClimate::TestReporter with SimpleCov [\#26](https://github.com/infinum/enumerations/pull/26) ([PetarCurkovic](https://github.com/PetarCurkovic)) 122 | - Add migration readme [\#24](https://github.com/infinum/enumerations/pull/24) ([Narayanan170](https://github.com/Narayanan170)) 123 | 124 | ## [v2.2.1](https://github.com/infinum/enumerations/tree/v2.2.1) (2016-09-20) 125 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.2.0...v2.2.1) 126 | 127 | **Closed issues:** 128 | 129 | - Add option to store foreign\_key as string [\#14](https://github.com/infinum/enumerations/issues/14) 130 | 131 | **Merged pull requests:** 132 | 133 | - Bugfix/empty initializer [\#17](https://github.com/infinum/enumerations/pull/17) ([PetarCurkovic](https://github.com/PetarCurkovic)) 134 | 135 | ## [v2.2.0](https://github.com/infinum/enumerations/tree/v2.2.0) (2016-09-19) 136 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.1.0...v2.2.0) 137 | 138 | **Merged pull requests:** 139 | 140 | - Feature/remove [\#16](https://github.com/infinum/enumerations/pull/16) ([PetarCurkovic](https://github.com/PetarCurkovic)) 141 | 142 | ## [v2.1.0](https://github.com/infinum/enumerations/tree/v2.1.0) (2016-08-19) 143 | [Full Changelog](https://github.com/infinum/enumerations/compare/v2.0.0...v2.1.0) 144 | 145 | **Closed issues:** 146 | 147 | - Moved all back to Base [\#9](https://github.com/infinum/enumerations/issues/9) 148 | - I18n.locale [\#6](https://github.com/infinum/enumerations/issues/6) 149 | - Case statement [\#3](https://github.com/infinum/enumerations/issues/3) 150 | 151 | **Merged pull requests:** 152 | 153 | - Feature/where filtering [\#13](https://github.com/infinum/enumerations/pull/13) ([PetarCurkovic](https://github.com/PetarCurkovic)) 154 | - Feature/localized attributes [\#12](https://github.com/infinum/enumerations/pull/12) ([PetarCurkovic](https://github.com/PetarCurkovic)) 155 | 156 | ## [v2.0.0](https://github.com/infinum/enumerations/tree/v2.0.0) (2016-08-17) 157 | [Full Changelog](https://github.com/infinum/enumerations/compare/v1.3.0...v2.0.0) 158 | 159 | **Merged pull requests:** 160 | 161 | - Update version to 2.0.0 [\#11](https://github.com/infinum/enumerations/pull/11) ([PetarCurkovic](https://github.com/PetarCurkovic)) 162 | - Refactor [\#10](https://github.com/infinum/enumerations/pull/10) ([PetarCurkovic](https://github.com/PetarCurkovic)) 163 | 164 | ## [v1.3.0](https://github.com/infinum/enumerations/tree/v1.3.0) (2016-08-11) 165 | [Full Changelog](https://github.com/infinum/enumerations/compare/v1.1.1...v1.3.0) 166 | 167 | ## [v1.1.1](https://github.com/infinum/enumerations/tree/v1.1.1) (2016-06-07) 168 | [Full Changelog](https://github.com/infinum/enumerations/compare/v1.1.0...v1.1.1) 169 | 170 | **Merged pull requests:** 171 | 172 | - Bugfix/value-id [\#8](https://github.com/infinum/enumerations/pull/8) ([nikajukic](https://github.com/nikajukic)) 173 | - Document configuration options [\#7](https://github.com/infinum/enumerations/pull/7) ([stankec](https://github.com/stankec)) 174 | - Refactoring all around [\#5](https://github.com/infinum/enumerations/pull/5) ([janvarljen](https://github.com/janvarljen)) 175 | - Added value method and support for custom attributes [\#4](https://github.com/infinum/enumerations/pull/4) ([PetarCurkovic](https://github.com/PetarCurkovic)) 176 | - Some refactoring and updated README [\#2](https://github.com/infinum/enumerations/pull/2) ([PetarCurkovic](https://github.com/PetarCurkovic)) 177 | 178 | ## [v1.1.0](https://github.com/infinum/enumerations/tree/v1.1.0) (2012-08-19) 179 | **Merged pull requests:** 180 | 181 | - Gemspec. [\#1](https://github.com/infinum/enumerations/pull/1) ([neektza](https://github.com/neektza)) 182 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Infinum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MIGRATE.md: -------------------------------------------------------------------------------- 1 | How to migrate from 1.x.x 2 | ========================= 3 | 4 | Changes in the namespace 5 | ======================== 6 | 7 | First off, start by renaming your namespaces, what used to be 8 | 9 | ```ruby 10 | class MyClass < Enumeration::Base 11 | ``` 12 | 13 | will now be 14 | 15 | ```ruby 16 | class MyClass < Enumerations::Base 17 | ``` 18 | 19 | Configuration 20 | ============= 21 | 22 | Since v2 has a breaking change of default values in the database columns, you have 2 options: 23 | 24 | 1. Create an initializer that you will configure to have the old naming conventions 25 | 2. Create data migrations and convert your data to be alligned with the new Enumerations 26 | 27 | First option might be tempting, but you need to weigh in the advantages of the new enumerations. 28 | With the second option, you can get enumerations in the database instead of ID's, which may prove quite usefull. 29 | 30 | First way 31 | ========= 32 | 33 | You need to create the following initializer 34 | 35 | ```ruby 36 | # config/initializers/enumerations.rb 37 | 38 | Enumerations.configure do |config| 39 | config.primary_key = :id 40 | config.foreign_key_suffix = :id 41 | end 42 | ``` 43 | 44 | And voilà! You can carry on with work knowing your enumerations are up to date. 45 | 46 | Second way 47 | ========== 48 | 49 | You can use a gem for the migration, or just write raw SQL. 50 | 51 | Your migrations will look something like this: 52 | 53 | First to change the column types 54 | ```ruby 55 | class MoveMyEnumToNewEnumerationsColumns 56 | def up 57 | rename_column :my_table, :my_enum_id, :my_enum 58 | change_column :my_table, :my_enum, :string 59 | end 60 | 61 | def down 62 | change_column :my_table, :my_enum, 'integer USING CAST(my_enum AS integer)' 63 | rename_column :my_table, :my_enum, :my_enum_id 64 | end 65 | end 66 | ``` 67 | 68 | And now for the actual data 69 | 70 | ```ruby 71 | class MoveMyEnumToNewEnumerations < ActiveRecord::Migration 72 | def up 73 | execute(<<-SQL 74 | UPDATE my_table 75 | SET my_enum = 76 | CASE my_enum 77 | WHEN '1' THEN 'first' 78 | WHEN '2' THEN 'second' 79 | WHEN '3' THEN 'third' 80 | END 81 | SQL 82 | ) 83 | end 84 | 85 | def down 86 | execute(<<-SQL 87 | UPDATE my_table 88 | SET my_enum = 89 | CASE my_enum 90 | WHEN 'first' THEN '1' 91 | WHEN 'second' THEN '2' 92 | WHEN 'third' THEN '3' 93 | END 94 | SQL 95 | ) 96 | end 97 | ``` 98 | 99 | And that's it, you won't need an initializer for this to work anymore, because these are the default options Enumeration gem ships with. 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Enumerations 2 | ============ 3 | 4 | [![Gem Version](https://badge.fury.io/rb/enumerations.svg)](https://badge.fury.io/rb/enumerations) 5 | [![Maintainability](https://api.codeclimate.com/v1/badges/c3b96c5afceaa9be2173/maintainability)](https://codeclimate.com/github/infinum/enumerations/maintainability) 6 | [![Test Coverage](https://api.codeclimate.com/v1/badges/c3b96c5afceaa9be2173/test_coverage)](https://codeclimate.com/github/infinum/enumerations/test_coverage) 7 | ![Build Status](https://github.com/infinum/enumerations/actions/workflows/test.yml/badge.svg) 8 | 9 | Rails plugin for Enumerations in ActiveRecord models. 10 | 11 | Installation 12 | ============ 13 | 14 | Inside your `Gemfile` add the following: 15 | 16 | ```ruby 17 | gem 'enumerations' 18 | ``` 19 | 20 | Usage 21 | ===== 22 | 23 | ## Defining enumerations 24 | 25 | Create a model for your enumerations: 26 | 27 | ```ruby 28 | class Status < Enumerations::Base 29 | values draft: { name: 'Draft' }, 30 | review_pending: { name: 'Review pending' }, 31 | published: { name: 'Published' } 32 | end 33 | ``` 34 | 35 | Or you can use `value` method for defining your enumerations: 36 | 37 | ```ruby 38 | class Status < Enumerations::Base 39 | value :draft, name: 'Draft' 40 | value :review_pending, name: 'Review pending' 41 | value :published, name: 'Published' 42 | end 43 | ``` 44 | 45 | Include enumerations for integer fields in other models: 46 | 47 | ```ruby 48 | class Post < ActiveRecord::Base 49 | enumeration :status 50 | 51 | validates :status, presence: true 52 | end 53 | ``` 54 | 55 | You can pass attributes to specify which enumeration and which column to use: 56 | 57 | ```ruby 58 | class Post < ActiveRecord::Base 59 | enumeration :status, 60 | foreign_key: :post_status, # specifies which column to use 61 | class_name: Post::Status # specifies the class of the enumerator 62 | 63 | validates :post_status, presence: true 64 | end 65 | ``` 66 | Attribute `foreign_key` you can pass as a `String` or a `Symbol`. Attribute `class_name` can be set as a `String`, a `Symbol` or a `String`. 67 | 68 | 69 | 70 | ## Setting enumeration value to objects 71 | 72 | Set enumerations: 73 | 74 | ```ruby 75 | @post = Post.first 76 | @post.status = Status.draft 77 | @post.save 78 | ``` 79 | 80 | Or you can set enumerations by `symbol`: 81 | 82 | ```ruby 83 | @post.status = Status.find(:draft) 84 | ``` 85 | 86 | > If you try to set value that is not an Enumeration value (except `nil`), you will get an `Enumerations::InvalidValueError` exception. You can turn this exception off in configuration. 87 | 88 | Also, you can set enumeration value like this: 89 | 90 | ```ruby 91 | @post.status_draft! 92 | ``` 93 | 94 | > When you include enumerations into your model, you'll get methods for setting each enumeration value. 95 | Each method name is consists from enumeration name and enumeration value name with **!** at the end. Examples: 96 | 97 | ```ruby 98 | class Post < ActiveRecord::Base 99 | enumeration :status 100 | end 101 | 102 | @post.status_draft! 103 | ``` 104 | 105 | ```ruby 106 | class User < ActiveRecord::Base 107 | enumeration :role 108 | end 109 | 110 | @user.role_admin! 111 | ``` 112 | 113 | ```ruby 114 | class User < ActiveRecord::Base 115 | enumeration :type, class_name: Role 116 | end 117 | 118 | @user.type_editor! 119 | ``` 120 | 121 | 122 | 123 | ## Finder methods 124 | 125 | Find enumerations by `id`: 126 | 127 | ```ruby 128 | @post.status = Status.find(2) # => Review pending 129 | @post.save 130 | ``` 131 | 132 | Other finding methods: 133 | 134 | ```ruby 135 | # Find by key as a Symbol 136 | Status.find(:review_pending) # => Review pending 137 | 138 | # Find by key as a String 139 | Status.find('draft') # => Draft 140 | 141 | # Find by multiple attributes 142 | Status.find_by(name: 'None', visible: true) # => None 143 | ``` 144 | 145 | Compare enumerations: 146 | 147 | ```ruby 148 | @post.status == :published # => true 149 | @post.status == 'published' # => true 150 | @post.status == Status.published # => true 151 | @post.status.published? # => true 152 | ``` 153 | 154 | Get all enumerations: 155 | 156 | ```ruby 157 | Status.all 158 | ``` 159 | 160 | 161 | 162 | ## Filtering methods 163 | 164 | Enumerations can be filtered with `where` method, similar to `ActiveRecord::QueryMethods#where`. 165 | 166 | ```ruby 167 | Role.where(admin: true) # => [Role.admin, Role.editor] 168 | Role.where(admin: true, active: true) # => [Role.admin] 169 | ``` 170 | 171 | 172 | 173 | ## Scopes on model 174 | 175 | With enumerations, you'll get scope for each enumeration value in the 176 | following format: 177 | 178 | ```ruby 179 | with_#{enumeration_name}_#{enumeration_value_name} 180 | without_#{enumeration_name}_#{enumeration_value_name} 181 | ``` 182 | 183 | or you can use the following scope and pass an array of enumerations: 184 | 185 | ```ruby 186 | with_#{enumeration_name}(enumeration, ...) 187 | without_#{enumeration_name}(enumeration, ...) 188 | ``` 189 | 190 | Examples: 191 | 192 | ```ruby 193 | class Post < ActiveRecord::Base 194 | enumeration :status 195 | end 196 | 197 | Post.with_status_draft # => <#ActiveRecord::Relation []> 198 | Post.without_status_review_pending # => <#ActiveRecord::Relation []> 199 | Post.with_status(:draft) # => <#ActiveRecord::Relation []> 200 | Post.without_status(:draft) # => <#ActiveRecord::Relation []> 201 | Post.with_status(Status.draft) # => <#ActiveRecord::Relation []> 202 | Post.with_status(:draft, :review_pending) # => <#ActiveRecord::Relation []> 203 | Post.with_status(Status.draft, 'published') # => <#ActiveRecord::Relation []> 204 | Post.with_status([:draft, :review_pending]) # => <#ActiveRecord::Relation []> 205 | ``` 206 | 207 | ```ruby 208 | class Post < ActiveRecord::Base 209 | enumeration :my_status, class_name: Status 210 | end 211 | 212 | Post.with_my_status_draft # => <#ActiveRecord::Relation []> 213 | Post.with_my_status_review_pending # => <#ActiveRecord::Relation []> 214 | Post.with_my_status(:draft) # => <#ActiveRecord::Relation []> 215 | Post.without_my_status(:draft) # => <#ActiveRecord::Relation []> 216 | ``` 217 | 218 | Each scope returns all records with specified enumeration value. 219 | 220 | 221 | 222 | ## Forms usage 223 | 224 | Use in forms: 225 | 226 | ```ruby 227 | %p 228 | = f.label :status 229 | %br 230 | = f.collection_select :status, Status.all, :symbol, :name 231 | ``` 232 | 233 | 234 | 235 | ## Validating input 236 | 237 | Enumerations will by default raise an exception if you try to set an invalid value. This prevents usage of validations, which you might want to add if you're developing an API and have to return meaningful errors to API clients. 238 | 239 | You can enable validations by first disabling error raising on invalid input (see [configuration](#configuration)). Then, you should add an inclusion validation to enumerated attributes: 240 | ```ruby 241 | class Post < ActiveRecord::Base 242 | enumeration :status 243 | 244 | validates :status, inclusion: { in: Status.all } 245 | end 246 | ``` 247 | 248 | You'll now get an appropriate error message when you insert an invalid value: 249 | ```ruby 250 | > post = Post.new(status: 'invalid') 251 | > post.valid? 252 | => false 253 | > post.errors.full_messages.to_sentence 254 | => "Status is not included in the list" 255 | > post.status 256 | => "invalid" 257 | ``` 258 | 259 | Advanced Usage 260 | ===== 261 | 262 | Except `name` you can specify any other attributes to your enumerations: 263 | 264 | ```ruby 265 | class Status < Enumerations::Base 266 | value :draft, id: 1, name: 'Draft', published: false 267 | value :review_pending, id: 2, name: 'Review pending', description: 'Some description...' 268 | value :published, id: 3, name: 'Published', published: true 269 | value :other # passing no attributes is also allowed 270 | end 271 | ``` 272 | 273 | Every enumeration has `id`, `name`, `description` and `published` methods. 274 | If you call method that is not in attribute list for enumeration, it will return `nil`. 275 | 276 | ```ruby 277 | Status.review_pending.description # => 'Some description...' 278 | Status.draft.description # => nil 279 | ``` 280 | 281 | For each attribute, you have predicate method. Predicate methods are actually calling `present?` 282 | method on attribute value: 283 | 284 | ```ruby 285 | Status.draft.name? # => true 286 | Status.draft.published? # => false 287 | Status.published.published? # => true 288 | Status.other.name? # => false 289 | ``` 290 | 291 | Translations 292 | ===== 293 | 294 | **Enumerations** uses power of I18n API (if translate_attributes configuration is set to true) to enable you to create a locale file 295 | for enumerations like this: 296 | 297 | ```yaml 298 | --- 299 | en: 300 | enumerations: 301 | status: 302 | draft: 303 | name: Draft 304 | description: Article draft... 305 | ... 306 | role: 307 | admin: 308 | name: Administrator 309 | ``` 310 | 311 | > You can separate enumerations locales into a separate `*.yml` files. 312 | > Then you should add locale file paths to I18n load path: 313 | 314 | ```ruby 315 | # config/initializers/locale.rb 316 | 317 | # Where the I18n library should search for translation files (e.g.): 318 | I18n.load_path += Dir[Rails.root.join('config', 'locales', 'enumerations', '*.yml')] 319 | ``` 320 | 321 | Configuration 322 | ============= 323 | 324 | Basically no configuration is needed. 325 | 326 | **Enumerations** has four configuration options. 327 | You can customize primary key, foreign key suffix, whether to translate attributes and whether to raise `Enumerations::InvalidValueError` exception when setting invalid values. 328 | Just add initializer file to `config/initializers/enumerations.rb`. 329 | 330 | Example of configuration: 331 | 332 | ```ruby 333 | # config/initializers/enumerations.rb 334 | 335 | Enumerations.configure do |config| 336 | config.primary_key = :id 337 | config.foreign_key_suffix = :id 338 | config.translate_attributes = true 339 | config.raise_invalid_value_error = true 340 | end 341 | ``` 342 | 343 | By default, `primary_key` and `foreign_key_suffix` are set to `nil`. 344 | 345 | By default model enumeration value is saved to column with same name as enumeration. 346 | If you set enumeration as: 347 | ```ruby 348 | enumeration :status 349 | ``` 350 | then model should have `status` column (as `String` type). 351 | If you want save an `ID` to this column, you can set `foreign_key_suffix` to `id`. 352 | Then model should have `status_id` column. 353 | 354 | If you set `primary_key` then you need provide this attribute for all enumerations values. 355 | Also, value from `primary_key` attribute will be stored to model as enumeration value. 356 | 357 | For example: 358 | 359 | ```ruby 360 | # with default configuration 361 | 362 | post = Post.new 363 | post.status = Status.draft # => post.status = 'draft' 364 | 365 | # with configured primary_key and foreign_key_suffix: 366 | 367 | Enumerations.configure do |config| 368 | config.primary_key = :id 369 | config.foreign_key_suffix = :id 370 | end 371 | 372 | class Status < Enumerations::Base 373 | value :draft, id: 1, name: 'Draft' 374 | value :review_pending, id: 2, name: 'Review pending', 375 | value :published, id: 3, name: 'Published', published: true 376 | end 377 | 378 | post = Post.new 379 | post.status = Status.draft # => post.status_id = 1 380 | ``` 381 | 382 | If you want to configure primary key or foreign key suffix per enumeration class, you can use `primary_key=` and `foreign_key_suffix=` class method: 383 | 384 | ```ruby 385 | class Status < Enumerations::Base 386 | self.primary_key = :id 387 | self.foreign_key_suffix = :id 388 | 389 | value :draft, id: 1, name: 'Draft' 390 | value :review_pending, id: 2, name: 'Review pending' 391 | end 392 | ``` 393 | 394 | Database Enumerations 395 | ===================== 396 | 397 | By default, enumeration values are saved to database as `String`. 398 | If you want, you can define `Enum` type in database: 399 | 400 | ```sql 401 | CREATE TYPE status AS ENUM ('draft', 'review_pending', 'published'); 402 | ``` 403 | 404 | Then you'll have enumeration as type in database and you can use it in database migrations: 405 | 406 | ```ruby 407 | add_column :posts, :status, :status, index: true 408 | ``` 409 | 410 | With configuration option `primary_key`, you can store any type you want (e.g. `Integer`). 411 | 412 | Also, for performance reasons, you should add indices to enumeration column. 413 | 414 | [Here](https://www.postgresql.org/docs/9.1/static/datatype-enum.html) you can find more informations about ENUM types. 415 | 416 | 417 | 418 | Contributing 419 | ============ 420 | 421 | 1. Fork it 422 | 2. Create your feature branch (`git checkout -b my-new-feature`) 423 | 3. Commit your changes (`git commit -am 'Add some feature'`) 424 | 4. Push to the branch (`git push origin my-new-feature`) 425 | 5. Create new Pull Request 426 | 427 | Credits 428 | ======= 429 | **Enumerations** is maintained and sponsored by [Infinum](https://infinum.co) 430 | 431 | Copyright © 2010 - 2018 Infinum Ltd. 432 | 433 | License 434 | ======= 435 | 436 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 437 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'bundler/gem_tasks' 4 | 5 | desc 'Default: run unit tests.' 6 | task default: :test 7 | 8 | desc 'Run unit tests.' 9 | Rake::TestTask.new(:test) do |t| 10 | t.libs << 'lib' 11 | t.pattern = 'test/**/*_test.rb' 12 | t.verbose = true 13 | end 14 | -------------------------------------------------------------------------------- /enumerations.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/enumerations/version', __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'enumerations' 5 | s.version = Enumerations::VERSION 6 | s.date = '2017-09-08' 7 | s.summary = 'Enumerations for ActiveRecord!' 8 | s.description = 'Extends ActiveRecord with enumeration capabilites.' 9 | s.homepage = 'https://github.com/infinum/enumerations' 10 | s.license = 'MIT' 11 | 12 | s.authors = [ 13 | 'Tomislav Car', 'Nikica Jokić', 'Nikola Santić', 'Stjepan Hadjić', 'Petar Ćurković' 14 | ] 15 | 16 | s.email = [ 17 | 'tomislav@infinum.hr', 'nikica.jokic@infinum.hr', 'nikola.santic@infinum.hr', 18 | 'stjepan.hadjic@infinum.hr', 'petar.curkovic@infinum.hr' 19 | ] 20 | 21 | s.add_dependency 'activerecord' 22 | s.add_dependency 'activesupport' 23 | s.add_dependency 'i18n' 24 | s.add_development_dependency 'pry-byebug' 25 | s.add_development_dependency 'rake' 26 | s.add_development_dependency 'simplecov' 27 | s.add_development_dependency 'sqlite3' 28 | 29 | s.files = `git ls-files`.split("\n") 30 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 31 | end 32 | -------------------------------------------------------------------------------- /lib/enumerations.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | require 'active_support/concern' 3 | require 'active_support/core_ext/class/attribute' 4 | require 'active_support/core_ext/string/inflections' 5 | 6 | require 'enumerations/configuration' 7 | require 'enumerations/version' 8 | require 'enumerations/base' 9 | require 'enumerations/reflection' 10 | require 'enumerations/errors' 11 | 12 | module Enumerations 13 | extend ActiveSupport::Concern 14 | 15 | included do 16 | class_attribute :_enumerations 17 | self._enumerations = [] 18 | end 19 | 20 | module ClassMethods 21 | # Create an enumeration for the symbol name. 22 | # Options include foreign_key attribute and class_name 23 | # 24 | # Example: 25 | # 26 | # class User < ActiveRecord::Base 27 | # enumeration :role 28 | # end 29 | # 30 | # user.role => # 31 | # 32 | # user.role = Role.staff 33 | # 34 | def enumeration(name, options = {}) 35 | reflection = Reflection.new(name, options) 36 | 37 | add_enumeration(reflection) 38 | end 39 | 40 | # Output all the enumerations that this model has defined 41 | # Returns an array of Reflection objects for all the 42 | # enumerations in the class. 43 | # 44 | # Example: 45 | # 46 | # User.reflect_on_all_enumerations => # [ 47 | # #, 48 | # # 49 | # ] 50 | # 51 | def reflect_on_all_enumerations 52 | _enumerations 53 | end 54 | 55 | def fetch_foreign_key_values(reflection, *symbols) 56 | symbols.flatten.map do |symbol| 57 | enumeration_value = reflection.enumerator_class.find(symbol) 58 | 59 | enumeration_value && 60 | enumeration_value.send(reflection.enumerator_class.primary_key || :symbol) 61 | end 62 | end 63 | 64 | private 65 | 66 | def add_enumeration(reflection) 67 | define_getter_method(reflection) 68 | define_setter_method(reflection) 69 | define_bang_methods(reflection) 70 | define_scopes_for_each_enumeration_value(reflection) 71 | define_enumeration_scope(reflection) 72 | 73 | self._enumerations += [reflection] 74 | end 75 | 76 | # Getter for belongs_to 77 | # 78 | # Example: 79 | # 80 | # user.role = Role.admin 81 | # user.role => # 82 | # 83 | def define_getter_method(reflection) 84 | define_method(reflection.name) do 85 | reflection.enumerator_class.find(self[reflection.foreign_key]) || self[reflection.foreign_key] 86 | end 87 | end 88 | 89 | # Setter for belongs_to 90 | # 91 | # Example: 92 | # 93 | # user.role = Role.admin 94 | # 95 | def define_setter_method(reflection) 96 | define_method("#{reflection.name}=") do |other| 97 | enumeration_value = reflection.enumerator_class.find(other) 98 | 99 | if other.present? && enumeration_value.nil? 100 | raise Enumerations::InvalidValueError if Enumerations.configuration.raise_invalid_value_error 101 | 102 | self[reflection.foreign_key] = other 103 | else 104 | self[reflection.foreign_key] = 105 | enumeration_value && 106 | enumeration_value.send(reflection.enumerator_class.primary_key || :symbol) 107 | end 108 | end 109 | end 110 | 111 | # Add bang methods for setting all enumeration values. 112 | # All methods are prefixed with enumeration name. 113 | # 114 | # Example: 115 | # 116 | # user.role_admin! 117 | # user.role => # 118 | # 119 | def define_bang_methods(reflection) 120 | reflection.enumerator_class.all.each do |enumeration| 121 | define_method("#{reflection.name}_#{enumeration.to_sym}!") do 122 | send("#{reflection.name}=", enumeration) 123 | end 124 | end 125 | end 126 | 127 | # Scopes for enumerated ActiveRecord model. 128 | # Format of scope name is with_#{enumeration_name}_#{enumeration_value_name}. 129 | # 130 | # Example: 131 | # 132 | # User.with_role_admin => <#ActiveRecord::Relation []> 133 | # User.with_role_editor => <#ActiveRecord::Relation []> 134 | # 135 | def define_scopes_for_each_enumeration_value(reflection) 136 | reflection.enumerator_class.all.each do |enumeration| 137 | foreign_key = enumeration.send(reflection.enumerator_class.primary_key || :symbol) 138 | 139 | scope("with_#{reflection.name}_#{enumeration.symbol}", 140 | -> { where(reflection.foreign_key => foreign_key) }) 141 | 142 | scope("without_#{reflection.name}_#{enumeration.symbol}", 143 | -> { where.not(reflection.foreign_key => foreign_key) }) 144 | end 145 | end 146 | 147 | # Scope for enumerated ActiveRecord model. 148 | # Format of scope name is with_#{enumeration_name}(*enumerations). 149 | # 150 | # Example: 151 | # 152 | # User.with_role(:admin) => <#ActiveRecord::Relation []> 153 | # User.with_role(:admin, Role.editor) => <#ActiveRecord::Relation []> 154 | # 155 | def define_enumeration_scope(reflection) 156 | scope("with_#{reflection.name}", 157 | lambda do |*symbols| 158 | where(reflection.foreign_key => fetch_foreign_key_values(reflection, symbols)) 159 | end) 160 | 161 | scope("without_#{reflection.name}", 162 | lambda do |*symbols| 163 | where.not(reflection.foreign_key => fetch_foreign_key_values(reflection, symbols)) 164 | end) 165 | end 166 | end 167 | end 168 | 169 | # Extend ActiveRecord with Enumeration capabilites 170 | ActiveSupport.on_load(:active_record) do 171 | include Enumerations 172 | end 173 | -------------------------------------------------------------------------------- /lib/enumerations/base.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/class/attribute' 2 | require 'active_support/core_ext/string/inflections' 3 | require 'enumerations/value' 4 | require 'enumerations/finder_methods' 5 | 6 | module Enumerations 7 | class Base < ActiveSupport::Multibyte::Chars 8 | extend Enumerations::FinderMethods 9 | include Enumerations::Value 10 | 11 | class_attribute :_values, :_symbol_index, :_primary_key, :_foreign_key_suffix 12 | 13 | self._values = {} 14 | self._symbol_index = {} 15 | self._primary_key = nil 16 | self._foreign_key_suffix = nil 17 | 18 | # Adding new value to enumeration 19 | # 20 | # Example: 21 | # 22 | # value :admin, id: 1, name: 'Admin', description: 'Some description...' 23 | # 24 | # Role.find(:admin).name => # "Admin" 25 | # Role.find(:admin).description => # "Some description..." 26 | # 27 | def self.value(symbol, attributes = {}) 28 | validate_symbol_and_primary_key(symbol, attributes) 29 | 30 | self._values = _values.merge(symbol => new(symbol, attributes)) 31 | 32 | # Adds name base finder methods 33 | # 34 | # Example: 35 | # 36 | # Role.admin => # 37 | # Role.staff => # 38 | # 39 | singleton_class.send(:define_method, symbol) do 40 | find(symbol) 41 | end 42 | end 43 | 44 | # Adding multiple values to enumeration 45 | # 46 | # Example: 47 | # 48 | # values admin: { id: 1, name: 'Admin' }, 49 | # manager: { id: 2, name: 'Manager' }, 50 | # staff: { id: 3, name: 'Staff', description: 'Some description...' } 51 | # 52 | # Role.admin.id => # 1 53 | # Role.find(:manager).name => # "Manager" 54 | # Role.find(:manager).description => # "Some description..." 55 | # 56 | def self.values(values) 57 | values.each do |symbol, attributes| 58 | value(symbol, attributes) 59 | end 60 | end 61 | 62 | # Returns an array of all enumeration symbols 63 | # 64 | # Example: 65 | # 66 | # Role.symbols => # [:admin, :manager, :staff] 67 | # 68 | def self.symbols 69 | _values.keys 70 | end 71 | 72 | # Returns an array of all enumeration values 73 | # 74 | # Example: 75 | # 76 | # Role.all => # [#, 77 | # #, 78 | # #] 79 | # 80 | def self.all 81 | _values.values 82 | end 83 | 84 | # Sets primary key for enumeration class. 85 | # 86 | # Example: 87 | # 88 | # class Status < Enumeration::Base 89 | # primary_key = :id 90 | # end 91 | # 92 | def self.primary_key=(key) 93 | self._primary_key = key && key.to_sym 94 | end 95 | 96 | # Gets primary key for enumeration class. 97 | # 98 | # Example: 99 | # 100 | # Status.primary_key => # nil 101 | # 102 | # class Status < Enumeration::Base 103 | # primary_key = :id 104 | # end 105 | # 106 | # Status.primary_key => # :id 107 | # 108 | def self.primary_key 109 | _primary_key || Enumerations.configuration.primary_key 110 | end 111 | 112 | # Sets foreign key suffix for enumeration class. 113 | # 114 | # Example: 115 | # 116 | # class Status < Enumeration::Base 117 | # foreign_key_suffix = :id 118 | # end 119 | 120 | def self.foreign_key_suffix=(suffix) 121 | self._foreign_key_suffix = suffix && suffix.to_sym 122 | end 123 | 124 | # Gets foreign key suffix for enumeration class. 125 | # 126 | # Example: 127 | # 128 | # Status.foreign_key_suffix => # nil 129 | # 130 | # class Status < Enumeration::Base 131 | # foreign_key_suffix = :id 132 | # end 133 | # 134 | # Status.foreign_key_suffix => # :id 135 | # 136 | def self.foreign_key_suffix 137 | _foreign_key_suffix || Enumerations.configuration.foreign_key_suffix 138 | end 139 | 140 | def self.validate_symbol_and_primary_key(symbol, attributes) 141 | raise Enumerations::DuplicatedSymbolError if find(symbol) 142 | 143 | return if primary_key.nil? 144 | 145 | primary_key_value = attributes[primary_key] 146 | 147 | raise Enumerations::MissingPrimaryKeyError if primary_key_value.nil? 148 | raise Enumerations::DuplicatedPrimaryKeyError if find(primary_key_value) 149 | 150 | self._symbol_index = _symbol_index.merge(symbol => primary_key_value) 151 | end 152 | 153 | private_class_method :validate_symbol_and_primary_key 154 | 155 | attr_reader :symbol, :attributes 156 | 157 | def initialize(symbol, attributes) 158 | super(symbol.to_s) 159 | 160 | @symbol = symbol 161 | @attributes = attributes 162 | 163 | create_instance_methods 164 | end 165 | 166 | private 167 | 168 | def chars(string) 169 | string 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /lib/enumerations/configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Enumerations 4 | def self.configuration 5 | @configuration ||= Configuration.new 6 | end 7 | 8 | def self.configure 9 | yield(configuration) 10 | end 11 | 12 | def self.restore_default_configuration 13 | @configuration = nil 14 | end 15 | 16 | class Configuration 17 | attr_accessor :primary_key 18 | attr_accessor :foreign_key_suffix 19 | attr_accessor :translate_attributes 20 | attr_accessor :raise_invalid_value_error 21 | 22 | def initialize 23 | @primary_key = nil 24 | @foreign_key_suffix = nil 25 | @translate_attributes = true 26 | @raise_invalid_value_error = true 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/enumerations/errors.rb: -------------------------------------------------------------------------------- 1 | module Enumerations 2 | class Error < StandardError; end 3 | class DuplicatedSymbolError < Error; end 4 | class MissingPrimaryKeyError < Error; end 5 | class DuplicatedPrimaryKeyError < Error; end 6 | class InvalidValueError < Error; end 7 | end 8 | -------------------------------------------------------------------------------- /lib/enumerations/finder_methods.rb: -------------------------------------------------------------------------------- 1 | module Enumerations 2 | module FinderMethods 3 | # Finds an enumeration by symbol or name 4 | # 5 | # Example: 6 | # 7 | # Role.find(:admin) => # 8 | # Role.find('staff') => # 9 | # 10 | def find(key) 11 | case key 12 | when Symbol, String, Enumerations::Base then find_by_key(key.to_sym) 13 | end || find_by_primary_key(key) 14 | end 15 | 16 | # Finds all enumerations which meets given attributes. 17 | # Similar to ActiveRecord::QueryMethods#where. 18 | # 19 | # Example: 20 | # 21 | # Role.find_by(name: 'Admin') => # 22 | # 23 | def where(**args) 24 | _values.values.select { |value| args.map { |k, v| value.attributes[k] == v }.all? } 25 | end 26 | 27 | # Finds an enumeration by defined attribute. Similar to ActiveRecord::FinderMethods#find_by 28 | # 29 | # Example: 30 | # 31 | # Role.find_by(name: 'Admin') => # 32 | # 33 | def find_by(**args) 34 | where(**args).first 35 | end 36 | 37 | def find_by_key(key) 38 | _values[key] 39 | end 40 | 41 | def find_by_primary_key(primary_key) 42 | return value_from_symbol_index(primary_key.to_i) if primary_key.is_a?(String) 43 | 44 | value_from_symbol_index(primary_key) 45 | end 46 | 47 | private 48 | 49 | def value_from_symbol_index(key) 50 | _values[_symbol_index.key(key)] 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/enumerations/reflection.rb: -------------------------------------------------------------------------------- 1 | module Enumerations 2 | class Reflection 3 | attr_reader :name, :options 4 | 5 | def initialize(name, options = {}) 6 | @name = name 7 | @options = options 8 | end 9 | 10 | def class_name 11 | @class_name ||= (options[:class_name] || name).to_s.camelize 12 | end 13 | 14 | def foreign_key 15 | @foreign_key ||= (options[:foreign_key] || default_foreign_key_name).to_sym 16 | end 17 | 18 | def enumerator_class 19 | @enumerator_class ||= class_name.constantize 20 | end 21 | 22 | private 23 | 24 | def default_foreign_key_name 25 | [name, enumerator_class.foreign_key_suffix].compact.join('_') 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/enumerations/value.rb: -------------------------------------------------------------------------------- 1 | module Enumerations 2 | module Value 3 | def to_i 4 | _values.keys.index(symbol) + 1 5 | end 6 | 7 | def to_s 8 | symbol.to_s 9 | end 10 | 11 | def to_param 12 | to_s 13 | end 14 | 15 | def to_sym 16 | symbol 17 | end 18 | 19 | # Comparison by symbol or object 20 | # 21 | # Example: 22 | # 23 | # Role.admin == :admin => true 24 | # Role.admin == Role.admin => true 25 | # Role.admin == :staff => false 26 | # Role.admin == Role.staff => false 27 | # 28 | # TODO: test if case..when is working with this 29 | def ==(other) 30 | case other 31 | when String then other == to_s 32 | when Symbol then other == symbol 33 | else super 34 | end 35 | end 36 | 37 | private 38 | 39 | def create_instance_methods 40 | define_attributes_getters 41 | define_value_checking_method 42 | define_predicate_methods_for_attributes 43 | end 44 | 45 | # Getters for all attributes 46 | # 47 | # Example: 48 | # 49 | # Role.admin => #1, :name=>"Admin", :description=>"Some description..."}> 51 | # user.role.id => # 1 52 | # user.role.name => # "Admin" 53 | # user.role.description => # "Some description..." 54 | # 55 | def define_attributes_getters 56 | @attributes.each do |key, _| 57 | next if self.class.method_defined?(key) 58 | 59 | self.class.send :define_method, key do |locale: I18n.locale| 60 | case @attributes[key] 61 | when String, Symbol then translate_attribute(key, locale) 62 | else @attributes[key] 63 | end 64 | end 65 | end 66 | end 67 | 68 | def translate_attribute(key, locale) 69 | return @attributes[key].to_s unless Enumerations.configuration.translate_attributes 70 | 71 | I18n.t(key, scope: [:enumerations, self.class.name.demodulize.underscore, symbol], 72 | default: @attributes[key].to_s, 73 | locale: locale) 74 | end 75 | 76 | # Predicate methods for values 77 | # 78 | # Example: 79 | # 80 | # user.role = Role.admin 81 | # user.role.admin? => # true 82 | # user.role.staff? => # false 83 | # 84 | def define_value_checking_method 85 | self.class.send :define_method, "#{symbol}?" do 86 | __callee__[0..-2].to_sym == symbol 87 | end 88 | end 89 | 90 | # Predicate methods for all attributes 91 | # 92 | # Example: 93 | # 94 | # class Role < Enumerations::Base 95 | # value :admin, name: 'Administrator', active: false 96 | # end 97 | # 98 | # user.role.name? => # true 99 | # user.role.admin? => # false 100 | # 101 | def define_predicate_methods_for_attributes 102 | @attributes.each do |key, _| 103 | method_name = "#{key}?" 104 | 105 | next if self.class.method_defined?(method_name.to_sym) 106 | 107 | self.class.send :define_method, method_name do 108 | @attributes[key].present? 109 | end 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/enumerations/version.rb: -------------------------------------------------------------------------------- 1 | module Enumerations 2 | VERSION = '2.6.0'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /test/base_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | 3 | class BaseTest < Minitest::Test 4 | def test_all 5 | statuses = Status.all 6 | 7 | assert_equal 5, statuses.size 8 | assert_equal statuses.first, Status.draft 9 | end 10 | 11 | def test_symbols 12 | status_symbols = Status.symbols 13 | 14 | assert_equal 5, status_symbols.size 15 | assert_equal status_symbols.first, Status.draft.to_sym 16 | end 17 | 18 | def test_duplicated_symbol 19 | assert_raises Enumerations::DuplicatedSymbolError do 20 | obj = Class.new(Enumerations::Base) 21 | 22 | obj.value :draft, id: 1, name: 'Draft' 23 | obj.value :draft, id: 2, name: 'Draft Again' 24 | end 25 | end 26 | 27 | def test_all_has_custom_attributes 28 | statuses = Status.all 29 | 30 | assert_silent do 31 | statuses.map(&:visible) 32 | statuses.map(&:deleted) 33 | end 34 | end 35 | 36 | def test_enumerations_custom_instance_method 37 | role = Role.find(:admin) 38 | 39 | assert_equal 'user_Admin', role.my_custom_name 40 | end 41 | 42 | def test_all_enumerations_has_custom_instance_methods 43 | roles = Role.all 44 | 45 | assert_silent do 46 | roles.map(&:my_custom_name) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/configuration/configuration.rb: -------------------------------------------------------------------------------- 1 | module Configuration 2 | Enumerations.configure do |config| 3 | config.primary_key = :id 4 | config.foreign_key_suffix = :id 5 | end 6 | 7 | ::CustomEnum = Class.new(Enumerations::Base) 8 | 9 | CustomEnum.values draft: { id: 1, name: 'Draft' }, 10 | review: { id: 2, name: 'Review' }, 11 | published: { id: 3, name: 'Published', published: true } 12 | 13 | ::CustomModel = Class.new(ActiveRecord::Base) 14 | 15 | CustomModel.enumeration :custom_enum 16 | 17 | Enumerations.restore_default_configuration 18 | end 19 | -------------------------------------------------------------------------------- /test/configuration/custom_configuration_test.rb: -------------------------------------------------------------------------------- 1 | module Configuration 2 | ::CustomConfigurationEnum = Class.new(Enumerations::Base) 3 | 4 | CustomConfigurationEnum.primary_key = :id 5 | CustomConfigurationEnum.value :draft, id: 1 6 | 7 | ::CustomConfigurationModel = Class.new(ActiveRecord::Base) 8 | CustomConfigurationModel.table_name = :custom_models 9 | CustomConfigurationModel.enumeration :custom_configuration_enum, foreign_key: :custom_enum_id 10 | 11 | class CustomConfigurationTest < Minitest::Test 12 | def test_primary_key_value 13 | assert_equal :id, CustomConfigurationEnum.primary_key 14 | end 15 | 16 | def test_primary_key_value_is_not_set_in_configuration 17 | assert_nil Enumerations.configuration.primary_key 18 | end 19 | 20 | def test_value_set_for_custom_foreign_key_config 21 | model = CustomConfigurationModel.new 22 | model.custom_configuration_enum = :draft 23 | 24 | assert_equal :draft, model.custom_configuration_enum.symbol 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/configuration/enumerations_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../helpers/test_helper' 2 | require_relative 'configuration' 3 | 4 | module Configuration 5 | class EnumerationsTest < Minitest::Test 6 | def setup 7 | Enumerations.configure do |config| 8 | config.primary_key = :id 9 | config.foreign_key_suffix = :id 10 | end 11 | end 12 | 13 | def teardown 14 | Enumerations.restore_default_configuration 15 | end 16 | 17 | def test_model_enumeration_assignment 18 | model = CustomModel.new 19 | model.custom_enum = CustomEnum.draft 20 | 21 | assert_equal 'draft', model.custom_enum.to_s 22 | end 23 | 24 | def test_model_bang_assignment 25 | model = CustomModel.new 26 | model.custom_enum_draft! 27 | 28 | assert_equal 'draft', model.custom_enum.to_s 29 | end 30 | 31 | def test_model_via_symbol_assignment 32 | model = CustomModel.new 33 | model.custom_enum = CustomEnum.published.symbol 34 | 35 | assert_equal 'published', model.custom_enum.to_s 36 | end 37 | 38 | def test_model_via_foreign_key_assignment 39 | model = CustomModel.new 40 | model.custom_enum_id = CustomEnum.published.id 41 | 42 | assert_equal 'published', model.custom_enum.to_s 43 | end 44 | 45 | def test_enumerated_class_scope_hash_value 46 | query_hash = CustomModel.with_custom_enum_draft.where_values_hash.symbolize_keys 47 | 48 | assert_equal query_hash, custom_enum_id: 1 49 | end 50 | 51 | def test_enumerations_overrides 52 | assert_equal :id, OverridableStatus.primary_key 53 | assert_equal :id, OverridableStatus.foreign_key_suffix 54 | 55 | model = OverridableModel.new 56 | model.overridable_status = OverridableStatus.draft 57 | 58 | assert_equal 1, model.overridable_status_id 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/configuration/finder_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../helpers/test_helper' 2 | require_relative 'configuration' 3 | 4 | module Configuration 5 | class FinderTest < Minitest::Test 6 | def setup 7 | Enumerations.configure do |config| 8 | config.primary_key = :id 9 | config.foreign_key_suffix = :id 10 | end 11 | end 12 | 13 | def teardown 14 | Enumerations.restore_default_configuration 15 | end 16 | 17 | def test_lookup_by_key 18 | enum = CustomEnum.find(:draft) 19 | 20 | assert_equal :draft, enum.symbol 21 | end 22 | 23 | def test_lookup_by_string_key 24 | enum = CustomEnum.find('draft') 25 | 26 | assert_equal :draft, enum.symbol 27 | end 28 | 29 | def test_lookup_by_primary_key 30 | enum = CustomEnum.find(1) 31 | 32 | assert_equal :draft, enum.symbol 33 | end 34 | 35 | def test_lookup_by_primary_key_as_string 36 | enum = CustomEnum.find('1') 37 | 38 | assert_equal :draft, enum.symbol 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/configuration_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | 3 | class ConfigurationTest < Minitest::Test 4 | def setup 5 | Enumerations.restore_default_configuration 6 | end 7 | 8 | def teardown 9 | Enumerations.restore_default_configuration 10 | end 11 | 12 | def test_default_configuration 13 | assert_nil Enumerations.configuration.primary_key 14 | assert_nil Enumerations.configuration.foreign_key_suffix 15 | end 16 | 17 | def test_custom_configuration 18 | Enumerations.configure do |config| 19 | config.primary_key = :id 20 | config.foreign_key_suffix = :id 21 | end 22 | 23 | assert_equal :id, Enumerations.configuration.primary_key 24 | assert_equal :id, Enumerations.configuration.foreign_key_suffix 25 | end 26 | 27 | def test_reflection_with_configured_foreign_key_suffix 28 | Enumerations.configure { |config| config.foreign_key_suffix = :id } 29 | 30 | reflection = Enumerations::Reflection.new(:status) 31 | 32 | assert_equal 'Status', reflection.class_name 33 | assert_equal ::Status, reflection.enumerator_class 34 | assert_equal :status_id, reflection.foreign_key 35 | end 36 | 37 | def test_required_primary_key_when_primary_key_configured 38 | Enumerations.configure { |config| config.primary_key = :id } 39 | 40 | assert_raises Enumerations::MissingPrimaryKeyError, 'Enumeration primary key is required' do 41 | Class.new(Enumerations::Base).value :draft, name: 'Draft' 42 | end 43 | end 44 | 45 | def test_duplicated_primary_key_when_primary_key_configured 46 | Enumerations.configure { |config| config.primary_key = :id } 47 | 48 | assert_raises Enumerations::DuplicatedPrimaryKeyError, 'Duplicate primary key 1' do 49 | Class.new(Enumerations::Base).values draft: { id: 1, name: 'Draft' }, 50 | test: { id: 1, name: 'Draft' } 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/enumerations_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | require 'enumerations/errors' 3 | 4 | class EnumerationsTest < Minitest::Test 5 | def test_reflect_on_all_enumerations 6 | enumerations = Post.reflect_on_all_enumerations 7 | 8 | assert_equal 2, enumerations.size 9 | assert_equal :status, enumerations.first.name 10 | assert_equal 'Status', enumerations.first.class_name 11 | 12 | assert_equal :some_other_status, enumerations[1].foreign_key 13 | end 14 | 15 | def test_model_enumeration_assignment 16 | p = Post.new 17 | p.status = Status.draft 18 | 19 | assert_equal 'draft', p.status.to_s 20 | end 21 | 22 | def test_model_bang_assignment 23 | p = Post.new 24 | p.status_draft! 25 | 26 | assert_equal 'draft', p.status.to_s 27 | end 28 | 29 | def test_model_uniqueness_validation 30 | p = Post.new(status: :draft) 31 | 32 | assert_equal true, p.valid? 33 | end 34 | 35 | def test_model_bang_assignment_with_custom_name 36 | p = Post.new 37 | p.different_status_draft! 38 | 39 | assert_equal 'draft', p.different_status.to_s 40 | end 41 | 42 | def test_model_via_symbol_assignment 43 | p = Post.new 44 | p.some_other_status = Status.published.symbol 45 | 46 | assert_equal 'published', p.some_other_status.to_s 47 | end 48 | 49 | def test_boolean_lookup 50 | p = Post.new 51 | p.status = Status.draft 52 | 53 | assert_equal true, p.status.draft? 54 | end 55 | 56 | def test_false_boolean_lookup 57 | p = Post.new 58 | p.status = Status.draft 59 | 60 | assert_equal false, p.status.published? 61 | end 62 | 63 | def test_multiple_enumerations_on_model 64 | enumerations = User.reflect_on_all_enumerations 65 | 66 | assert_equal 2, enumerations.size 67 | 68 | assert_equal :role, enumerations.first.name 69 | assert_equal :role, enumerations.first.foreign_key 70 | 71 | assert_equal :status, enumerations[1].name 72 | assert_equal :status, enumerations[1].foreign_key 73 | end 74 | 75 | def test_multiple_enumeration_assignments_on_model 76 | u = User.new 77 | u.role = Role.admin 78 | u.status = Status.published 79 | 80 | assert_equal 'admin', u.role.to_s 81 | assert_equal 'published', u.status.to_s 82 | end 83 | 84 | def test_enumerated_class_has_enumeration_scope 85 | assert_respond_to User, :with_role 86 | end 87 | 88 | def test_enumerated_class_has_scopes 89 | Role.all.each do |role| 90 | assert_respond_to User, ['with_role', role.to_s].join('_').to_sym 91 | end 92 | end 93 | 94 | def test_enumerated_class_enumeration_scope_hash_value 95 | query_hash = User.with_role(:admin).where_values_hash.symbolize_keys 96 | 97 | assert_equal query_hash, role: :admin 98 | end 99 | 100 | def test_enumerated_class_enumeration_without_scope_results 101 | User.create(role: :admin) 102 | User.create(role: :editor) 103 | 104 | results = User.without_role(:admin) 105 | 106 | assert_equal results, User.where.not(role: :admin) 107 | end 108 | 109 | def test_enumerated_class_enumeration_scope_hash_value_for_multiple_enums 110 | query_hash = User.with_role(:admin, Role.author, 'editor').where_values_hash.symbolize_keys 111 | 112 | assert_equal query_hash, role: [:admin, :author, :editor] 113 | end 114 | 115 | def test_enumerated_class_enumeration_scope_hash_value_for_multiple_enums_as_array 116 | query_hash = User.with_role([:admin, Role.author, 'editor']).where_values_hash.symbolize_keys 117 | 118 | assert_equal query_hash, role: [:admin, :author, :editor] 119 | end 120 | 121 | def test_enumerated_class_without_scope_hash_value 122 | query_hash = User.with_role_admin.where_values_hash.symbolize_keys 123 | 124 | assert_equal query_hash, role: :admin 125 | end 126 | 127 | def test_enumerated_class_without_scope_results 128 | User.create(role: :admin) 129 | User.create(role: :editor) 130 | 131 | results = User.without_role_admin 132 | 133 | assert_equal results, User.where.not(role: :admin) 134 | end 135 | 136 | def test_nonexistent_value_assignment 137 | assert_raises Enumerations::InvalidValueError do 138 | User.new(role: :nonexistent_value) 139 | end 140 | end 141 | 142 | def test_nonexistent_value_assignment_with_error_raising_turned_off 143 | Enumerations.configuration.raise_invalid_value_error = false 144 | 145 | user = User.new(role: :nonexistent_value) 146 | 147 | assert_equal user.role, 'nonexistent_value' 148 | 149 | user.role = :other_nonexistent_value 150 | 151 | assert_equal user.role, 'other_nonexistent_value' 152 | 153 | Enumerations.configuration.raise_invalid_value_error = true 154 | end 155 | 156 | def test_on_nil_value_assignment 157 | user = User.new(role: nil) 158 | assert_nil user.role 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /test/finder_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | 3 | class FinderTest < Minitest::Test 4 | def test_lookup_by_symbol 5 | status = Status.find(:draft) 6 | 7 | assert_equal :draft, status.symbol 8 | end 9 | 10 | def test_lookup_fail_by_symbol 11 | status = Status.find(:draft) 12 | 13 | refute_same :published, status.symbol 14 | end 15 | 16 | def test_lookup_by_string_key 17 | status = Status.find('draft') 18 | 19 | assert_equal :draft, status.symbol 20 | end 21 | 22 | def test_find_by 23 | status = Status.find_by(name: 'Draft') 24 | 25 | assert_equal :draft, status.symbol 26 | end 27 | 28 | def test_fail_find_by 29 | status = Status.find_by(name: 'Draft1') 30 | 31 | assert_nil status 32 | end 33 | 34 | def test_where_by_name 35 | statuses = Status.where(name: 'Draft') 36 | 37 | assert_equal [Status.find(:draft)], statuses 38 | end 39 | 40 | def test_where_by_name_with_no_results 41 | statuses = Status.where(name: 'Draft1') 42 | 43 | assert_equal [], statuses 44 | end 45 | 46 | def test_where_by_custom_attributes 47 | roles = Role.where(admin: true) 48 | 49 | assert_equal 2, roles.count 50 | assert_equal [:admin, :editor], roles.map(&:symbol) 51 | end 52 | 53 | def test_where_by_multiple_custom_attributes 54 | roles = Role.where(admin: true, active: true) 55 | 56 | assert_equal 1, roles.count 57 | assert_equal [:admin], roles.map(&:symbol) 58 | end 59 | 60 | def test_where_by_undefined_custom_attributes 61 | roles = Role.where(description1: false) 62 | 63 | assert_equal [], roles 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/helpers/database_helper.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') 4 | 5 | ActiveRecord::Schema.define do 6 | create_table :posts, force: true do |t| 7 | t.string :status 8 | t.string :some_other_status 9 | end 10 | 11 | create_table :users, force: true do |t| 12 | t.string :role 13 | t.string :status 14 | end 15 | 16 | create_table :custom_models, force: true do |t| 17 | t.integer :custom_enum_id 18 | t.integer :custom_enum 19 | end 20 | 21 | create_table :overridable_models, force: true do |t| 22 | t.integer :overridable_status_id 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/helpers/locale_helper.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | 3 | I18n.available_locales = [:en, :hr, :fr] 4 | I18n.load_path = Dir[File.join('test', 'locales', '*.yml')] 5 | -------------------------------------------------------------------------------- /test/helpers/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | require 'minitest/autorun' 5 | require 'enumerations' 6 | require 'active_record' 7 | require 'pry' 8 | 9 | require_relative 'database_helper' 10 | require_relative 'locale_helper' 11 | 12 | class Status < Enumerations::Base 13 | values draft: { id: 1, name: 'Draft' }, 14 | review_pending: { id: 2, name: 'Review pending' }, 15 | published: { id: 3, name: 'Published' } 16 | 17 | value :none, id: 4, name: 'None', visible: true, deleted: false 18 | value :deleted, id: 5, deleted: true 19 | end 20 | 21 | class Role < Enumerations::Base 22 | value :admin, name: 'Admin', admin: true, active: true 23 | value :editor, name: 'Editor', admin: true, active: false, description: 'Edits newspapers' 24 | value :author, name: 'Author' 25 | value :lecturer, type: :croatist 26 | 27 | def my_custom_name 28 | ['user', name].join('_') 29 | end 30 | end 31 | 32 | class Post < ActiveRecord::Base 33 | enumeration :status 34 | enumeration :different_status, foreign_key: :some_other_status, class_name: 'Status' 35 | 36 | validates :status, uniqueness: true 37 | end 38 | 39 | class User < ActiveRecord::Base 40 | enumeration :role 41 | enumeration :status 42 | end 43 | 44 | class OverridableStatus < Enumerations::Base 45 | self.primary_key = :id 46 | self.foreign_key_suffix = :id 47 | 48 | value :draft, id: 1 49 | end 50 | 51 | class OverridableModel < ActiveRecord::Base 52 | enumeration :overridable_status 53 | end 54 | -------------------------------------------------------------------------------- /test/locales/hr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | hr: 3 | enumerations: 4 | status: 5 | draft: 6 | name: Nacrt 7 | review_pending: 8 | name: Čeka pregled 9 | published: 10 | name: Published 11 | 12 | role: 13 | admin: 14 | name: Admin 15 | editor: 16 | name: Urednik 17 | description: Uređuje novine 18 | author: 19 | name: Autor 20 | description: Autor novina 21 | -------------------------------------------------------------------------------- /test/reflection_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | 3 | class ReflectionTest < Minitest::Test 4 | def test_reflection_with_all_attributes 5 | reflection = Enumerations::Reflection.new(:status, class_name: 'Status', 6 | foreign_key: :status) 7 | 8 | assert_equal :status, reflection.name 9 | assert_equal 'Status', reflection.class_name 10 | assert_equal :status, reflection.foreign_key 11 | assert_equal ::Status, reflection.enumerator_class 12 | end 13 | 14 | def test_reflection_without_class_name_and_foreign_key 15 | reflection = Enumerations::Reflection.new(:status) 16 | 17 | assert_equal :status, reflection.name 18 | assert_equal 'Status', reflection.class_name 19 | assert_equal :status, reflection.foreign_key 20 | assert_equal ::Status, reflection.enumerator_class 21 | end 22 | 23 | def test_reflection_with_custom_name_and_without_foreign_key 24 | reflection = Enumerations::Reflection.new(:my_status, class_name: 'Status') 25 | 26 | assert_equal :my_status, reflection.name 27 | assert_equal 'Status', reflection.class_name 28 | assert_equal :my_status, reflection.foreign_key 29 | assert_equal ::Status, reflection.enumerator_class 30 | end 31 | 32 | def test_reflection_with_class_name_as_constant 33 | reflection = Enumerations::Reflection.new(:status, class_name: Status) 34 | 35 | assert_equal 'Status', reflection.class_name 36 | assert_equal ::Status, reflection.enumerator_class 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/translation_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | 3 | class TranslationTest < Minitest::Test 4 | def test_translated_value_with_default_locale 5 | status = Status.find(:draft) 6 | 7 | assert_equal 'Draft', status.name 8 | end 9 | 10 | def test_translated_value_with_i18n_locale 11 | status = Status.find(:draft) 12 | 13 | I18n.locale = :hr 14 | translated_name = status.name 15 | I18n.locale = :en 16 | 17 | assert_equal 'Nacrt', translated_name 18 | end 19 | 20 | def test_translated_value_with_i18n_locales 21 | status = Status.find(:draft) 22 | 23 | I18n.locale = :hr 24 | assert_equal 'Nacrt', status.name 25 | I18n.locale = :en 26 | assert_equal 'Draft', status.name 27 | end 28 | 29 | def test_translated_value_with_custom_locale 30 | status = Status.find(:draft) 31 | 32 | assert_equal 'Nacrt', status.name(locale: :hr) 33 | end 34 | 35 | def test_boolean_value_true_not_changed_by_translations 36 | status = Status.find(:none) 37 | 38 | assert_equal true, status.visible 39 | end 40 | 41 | def test_boolean_value_false_not_changed_by_translations 42 | status = Status.find(:none) 43 | 44 | assert_equal false, status.deleted 45 | end 46 | 47 | def test_boolean_value_false_not_changed_by_translations_with_custom_locale 48 | status = Status.find(:none) 49 | 50 | assert_equal false, status.deleted(locale: :hr) 51 | end 52 | 53 | def test_translated_custom_attribute_with_i18n_locale 54 | role = Role.find(:editor) 55 | 56 | I18n.locale = :hr 57 | translated_value = role.description 58 | I18n.locale = :en 59 | 60 | assert_equal 'Uređuje novine', translated_value 61 | end 62 | 63 | def test_translated_custom_attribute_with_custom_locale 64 | role = Role.find(:editor) 65 | 66 | assert_equal 'Uređuje novine', role.description(locale: :hr) 67 | end 68 | 69 | def test_empty_custom_attribute_with_custom_locale 70 | role = Role.find(:admin) 71 | 72 | assert_nil role.description(locale: :hr) 73 | end 74 | 75 | def test_empty_custom_attribute_with_custom_locale_and_defined_translation 76 | role = Role.find(:author) 77 | 78 | assert_nil role.description(locale: :hr) 79 | end 80 | 81 | def test_find_by_localized_name 82 | status = Status.find_by(name: 'Draft') 83 | 84 | assert_equal :draft, status.symbol 85 | end 86 | 87 | def test_find_by_localized_name_with_i18n_locale 88 | I18n.locale = :hr 89 | status = Status.find_by(name: 'Nacrt') 90 | I18n.locale = :en 91 | 92 | assert_nil status 93 | end 94 | 95 | def test_turn_of_translate 96 | status = Status.find(:draft) 97 | Enumerations.configuration.translate_attributes = false 98 | name = status.name 99 | Enumerations.configuration.translate_attributes = true 100 | 101 | assert_equal name, 'Draft' 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /test/value_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helpers/test_helper' 2 | 3 | class ValueTest < Minitest::Test 4 | def test_equal_by_symbol 5 | status = Status.draft 6 | 7 | assert_equal true, status == :draft 8 | end 9 | 10 | def test_equal_by_string 11 | status = Status.draft 12 | 13 | assert_equal true, status == 'draft' 14 | end 15 | 16 | def test_equal_by_enumeration 17 | status = Status.draft 18 | 19 | assert_equal true, status == Status.draft 20 | end 21 | 22 | def test_not_equal_by_enumeration 23 | status = Status.draft 24 | 25 | assert_equal false, status == Status.published 26 | end 27 | 28 | def test_with_defined_custom_attributes_visible 29 | status = Status.find(:none) 30 | 31 | assert_equal true, status.visible 32 | end 33 | 34 | def test_with_defined_custom_attributes_deleted 35 | status = Status.find(:deleted) 36 | 37 | assert_equal true, status.deleted 38 | end 39 | 40 | def test_without_defined_custom_attributes 41 | status = Status.find(:draft) 42 | 43 | assert_nil status.visible 44 | end 45 | 46 | def test_enumeration_to_i_return_ordinal 47 | first_status = Status.find(:draft) 48 | second_status = Status.find(:review_pending) 49 | 50 | assert_equal first_status.to_i, 1 51 | assert_equal second_status.to_i, 2 52 | end 53 | 54 | def test_enumeration_to_sym 55 | status = Status.find(:draft) 56 | 57 | assert_equal status.to_sym, :draft 58 | end 59 | 60 | def test_enumeration_to_param 61 | status = Status.find(:draft) 62 | 63 | assert_equal status.to_param, 'draft' 64 | end 65 | 66 | def test_enumeration_when_attribute_value_is_symbol 67 | role = Role.find(:lecturer) 68 | 69 | assert_equal role.type, 'croatist' 70 | end 71 | 72 | def test_enumeration_attribute_predicate_methods 73 | status = Status.none 74 | 75 | assert_equal status.name?, true 76 | assert_equal status.visible?, true 77 | assert_equal status.deleted?, false 78 | end 79 | 80 | def test_enumeration_attribute_predicate_method_on_undefined_attribute 81 | status = Status.deleted 82 | 83 | assert_equal status.name?, false 84 | end 85 | 86 | def test_enumeration_raises_error_for_nonexistent_attribute 87 | enum = Class.new(Enumerations::Base) do 88 | value :foobar 89 | end 90 | 91 | assert_raises NoMethodError do 92 | enum.foobar.name 93 | end 94 | end 95 | 96 | def test_enumeration_does_not_respond_to_nonexistent_attribute 97 | enum = Class.new(Enumerations::Base) do 98 | value :foobar 99 | end 100 | 101 | assert_equal enum.foobar.respond_to?(:name), false 102 | end 103 | 104 | def test_string_methods_on_value 105 | enum = Class.new(Enumerations::Base) do 106 | value :foobar 107 | end 108 | 109 | assert_equal enum.foobar.upcase, 'FOOBAR' 110 | end 111 | end 112 | --------------------------------------------------------------------------------