├── .coveralls.yml ├── lib ├── symbolize │ ├── version.rb │ ├── railtie.rb │ ├── mongoid.rb │ └── active_record.rb └── symbolize.rb ├── .gitignore ├── Rakefile ├── spec ├── locales │ ├── en.yml │ └── pt.yml ├── db │ └── 001_create_testing_structure.rb ├── spec_helper.rb └── symbolize │ ├── mongoid_spec.rb │ └── active_record_spec.rb ├── Gemfile ├── .travis.yml ├── Guardfile ├── .rubocop.yml ├── .rubocop_todo.yml ├── MIT-LICENSE ├── symbolize.gemspec └── README.md /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci -------------------------------------------------------------------------------- /lib/symbolize/version.rb: -------------------------------------------------------------------------------- 1 | module Symbolize 2 | VERSION = '4.5.2' 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | coverage 3 | rdoc 4 | doc 5 | *.gem 6 | pkg 7 | .idea 8 | .bundle 9 | Gemfile.lock -------------------------------------------------------------------------------- /lib/symbolize.rb: -------------------------------------------------------------------------------- 1 | module Symbolize 2 | autoload :ActiveRecord, 'symbolize/active_record' 3 | autoload :Mongoid, 'symbolize/mongoid' 4 | end 5 | 6 | require 'symbolize/railtie' if defined? Rails 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | require 'rubocop/rake_task' 5 | 6 | RSpec::Core::RakeTask.new 7 | RuboCop::RakeTask.new 8 | 9 | task :default => [:spec, :rubocop] 10 | -------------------------------------------------------------------------------- /spec/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activerecord: 3 | attributes: 4 | symbolizes: 5 | user: 6 | language: 7 | pt: Portuguese 8 | en: English 9 | sex: 10 | "true": Female 11 | "false": Male 12 | user_skill: 13 | kind: 14 | magic: Magic 15 | agility: Agility 16 | -------------------------------------------------------------------------------- /lib/symbolize/railtie.rb: -------------------------------------------------------------------------------- 1 | # Rails 3 initialization 2 | module Symbolize 3 | require 'rails' 4 | class Railtie < Rails::Railtie 5 | initializer 'symbolize.insert_into_active_record' do 6 | ActiveSupport.on_load :active_record do 7 | ::ActiveRecord::Base.send :include, Symbolize::ActiveRecord 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem 'bson_ext', :platform => :ruby 7 | gem 'sqlite3', :platform => :ruby 8 | gem 'pg', :platform => :ruby 9 | 10 | gem 'activerecord-jdbcmysql-adapter', :platform => :jruby 11 | gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby 12 | 13 | gem 'coveralls', :require => false if ENV['CI'] 14 | end 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1 6 | - jruby 7 | - rbx-2 8 | - ruby-head 9 | - jruby-head 10 | jdk: 11 | - openjdk7 12 | - oraclejdk7 13 | env: JRUBY_OPTS='--server -Xcompile.invokedynamic=false -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-noverify -J-Xms512m -J-Xmx1024m' 14 | matrix: 15 | allow_failures: 16 | - rvm: rbx-2 17 | - rvm: ruby-head 18 | - rvm: jruby-head 19 | services: 20 | - mongodb 21 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # 2 | # Symbolize Guardfile 3 | # 4 | ignore(/\/.#.+/) 5 | 6 | # notification :off 7 | 8 | guard :rubocop, :all_on_start => false, :keep_failed => false, :notification => false, :cli => ['--format', 'emacs'] do 9 | watch(/^lib\/(.+)\.rb$/) 10 | end 11 | 12 | guard :rspec, :cmd => 'bundle exec rspec', :notification => true, :title => 'GeoRuby' do 13 | watch(/^spec\/.+_spec\.rb$/) 14 | watch(/^lib\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" } 15 | watch('spec/spec_helper.rb') { 'spec' } 16 | end 17 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | ActionFilter: 4 | EnforcedStyle: filter 5 | 6 | CaseIndentation: 7 | IndentWhenRelativeTo: end 8 | 9 | CollectionMethods: 10 | PreferredMethods: 11 | find: detect 12 | 13 | EmptyLineBetweenDefs: 14 | AllowAdjacentOneLineDefs: true 15 | 16 | Encoding: 17 | Enabled: false 18 | 19 | EndAlignment: 20 | AlignWith: variable 21 | 22 | HashSyntax: 23 | EnforcedStyle: hash_rockets 24 | 25 | Style/IndentHash: 26 | EnforcedStyle: consistent 27 | 28 | Loop: 29 | Enabled: false 30 | 31 | PredicateName: 32 | Enabled: false 33 | 34 | RegexpLiteral: 35 | Enabled: false 36 | 37 | Semicolon: 38 | AllowAsExpressionSeparator: true 39 | 40 | Style/TrailingComma: 41 | EnforcedStyleForMultiline: comma 42 | 43 | Style/TrivialAccessors: 44 | AllowDSLWriters: true 45 | AllowPredicates: true 46 | ExactNameMatch: true 47 | -------------------------------------------------------------------------------- /spec/db/001_create_testing_structure.rb: -------------------------------------------------------------------------------- 1 | class CreateTestingStructure < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :name, :so, :gui, :other, :language, :kind 5 | t.string :status, :default => :active 6 | t.string :limited, :limit => 10 7 | t.string :karma, :limit => 5 8 | t.boolean :sex 9 | t.boolean :public 10 | t.boolean :cool 11 | t.string :role 12 | t.string :country, :default => 'pt' 13 | t.string :some_attr # used in name collision tests 14 | end 15 | create_table :user_skills do |t| 16 | t.references :user 17 | t.string :kind 18 | end 19 | create_table :user_extras do |t| 20 | t.references :user 21 | t.string :key, :null => false 22 | end 23 | create_table :permissions do |t| 24 | t.string :name, :null => false 25 | t.string :kind, :null => false 26 | t.integer :lvl, :null => false 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/locales/pt.yml: -------------------------------------------------------------------------------- 1 | pt: 2 | activerecord: 3 | symbolizes: 4 | user: 5 | role: reader 6 | status: 7 | active: Active 8 | inactive: Inactive 9 | gui: 10 | cocoa: cocoa 11 | gtk: gtk 12 | qt: qt 13 | country: 14 | pt: Pt 15 | language: 16 | pt: Português 17 | en: Inglês 18 | sex: 19 | "true": Feminino 20 | "false": Masculino 21 | user_skill: 22 | kind: 23 | magic: Mágica 24 | agility: Agilidade 25 | errors: 26 | messages: &errors_messages 27 | record_invalid: Inválido 28 | mongoid: 29 | symbolizes: 30 | person: 31 | language: 32 | pt: Português 33 | en: Inglês 34 | sex: 35 | "true": Feminino 36 | "false": Masculino 37 | person_skill: 38 | kind: 39 | magic: Mágica 40 | agility: Agilidade 41 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2014-09-20 08:12:06 -0700 using RuboCop version 0.26.1. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | 8 | # Offense count: 2 9 | Metrics/BlockNesting: 10 | Max: 4 11 | 12 | # Offense count: 2 13 | Metrics/CyclomaticComplexity: 14 | Max: 20 15 | 16 | # Offense count: 74 17 | # Configuration parameters: AllowURI, URISchemes. 18 | Metrics/LineLength: 19 | Max: 161 20 | 21 | # Offense count: 3 22 | # Configuration parameters: CountComments. 23 | Metrics/MethodLength: 24 | Max: 85 25 | 26 | # Offense count: 2 27 | Metrics/PerceivedComplexity: 28 | Max: 25 29 | 30 | # Offense count: 14 31 | Style/Documentation: 32 | Enabled: false 33 | 34 | # Offense count: 2 35 | # Configuration parameters: MinBodyLength. 36 | Style/GuardClause: 37 | Enabled: false 38 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2008 Andreas Neuhaus 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /symbolize.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | 4 | require 'symbolize/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'symbolize' 8 | s.version = Symbolize::VERSION 9 | 10 | s.authors = ['Marcos Piccinini'] 11 | s.description = 'ActiveRecord/Mongoid enums with i18n' 12 | s.homepage = 'http://github.com/nofxx/symbolize' 13 | s.summary = 'Object enums with i18n in AR or Mongoid' 14 | s.email = 'x@nofxx.com' 15 | s.license = 'MIT' 16 | 17 | s.files = Dir.glob('{lib,spec}/**/*') + %w(README.md Rakefile) 18 | s.require_path = 'lib' 19 | 20 | s.add_dependency 'i18n' 21 | s.add_dependency 'activemodel', '>= 3.2', '< 5' 22 | s.add_dependency 'activesupport', '>= 3.2', '< 5' 23 | 24 | # s.add_development_dependency 'pg' 25 | # s.add_development_dependency 'sqlite3' 26 | s.add_development_dependency 'pry' 27 | s.add_development_dependency 'mongoid' 28 | s.add_development_dependency 'activerecord' 29 | # s.add_development_dependency 'coveralls' 30 | s.add_development_dependency 'rake' 31 | s.add_development_dependency 'rspec', '>= 3' 32 | s.add_development_dependency 'rubocop' 33 | s.add_development_dependency 'guard' 34 | s.add_development_dependency 'guard-rspec' 35 | s.add_development_dependency 'guard-rubocop' 36 | 37 | end 38 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | # require 'pry' 3 | begin 4 | require 'spec' 5 | rescue LoadError 6 | require 'rspec' 7 | end 8 | 9 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 10 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 11 | 12 | require 'i18n' 13 | I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locales', '*.{rb,yml}')] 14 | I18n.default_locale = 'pt' 15 | 16 | if ENV['CI'] 17 | require 'coveralls' 18 | Coveralls.wear! 19 | end 20 | 21 | # 22 | # Mongoid 23 | # 24 | unless ENV['ONLY_AR'] 25 | 26 | require 'mongoid' 27 | puts "Using Mongoid v#{Mongoid::VERSION}" 28 | 29 | Mongoid.configure do |config| 30 | # config.master = Mongo::Connection.new.db("symbolize_test") 31 | config.connect_to('symbolize_test') 32 | end 33 | 34 | require 'symbolize/mongoid' 35 | 36 | RSpec.configure do |config| 37 | config.before(:each) do 38 | Mongoid.purge! 39 | end 40 | end 41 | end 42 | 43 | # 44 | # ActiveRecord 45 | # 46 | unless ENV['ONLY_MONGOID'] 47 | 48 | require 'active_record' 49 | require 'symbolize/active_record' 50 | 51 | puts "Using ActiveRecord #{ActiveRecord::VERSION::STRING}" 52 | 53 | ActiveRecord::Base.send :include, Symbolize::ActiveRecord # this is normally done by the railtie 54 | 55 | ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:') # 'postgresql', :database => 'symbolize_test', :username => 'postgres') 56 | 57 | if ActiveRecord::VERSION::STRING >= '3.1' 58 | ActiveRecord::Migrator.migrate('spec/db') 59 | else 60 | require 'db/001_create_testing_structure' 61 | CreateTestingStructure.migrate(:up) 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Symbolize (attribute values) 2 | ========= 3 | 4 | # No longer maintained 5 | 6 | 7 | ## Please check this other gems: 8 | 9 | # Mongoid 10 | 11 | https://github.com/thetron/mongoid-enum 12 | 13 | 14 | # ActiveModel & POROs 15 | 16 | https://github.com/brainspec/enumerize 17 | 18 | 19 | This plugin introduces an easy way to use symbols for values of attributes. 20 | Symbolized attributes return a ruby symbol (or nil) as their value 21 | and can be set using :symbols or "strings". 22 | 23 | 24 | 25 | Install 26 | ------- 27 | 28 | Gem 29 | 30 | 31 | gem install symbolize 32 | 33 | 34 | Gemfile 35 | 36 | gem 'symbolize' 37 | 38 | 39 | About 40 | ----- 41 | 42 | Just use "symbolize :attribute" in your model, and the specified 43 | attribute will return symbol values and can be set using symbols 44 | (setting string values works, which is important when using forms). 45 | 46 | On schema DBs, the attribute should be a string (varchar) column. 47 | 48 | 49 | Usage 50 | ----- 51 | 52 | 53 | ActiveRecord: 54 | 55 | class Contact < ActiveRecord::Base 56 | 57 | symbolize :kind, :in => [:im, :mobile, :email], :scopes => true 58 | 59 | 60 | Mongoid: 61 | 62 | class Contact 63 | include Mongoid::Symbolize 64 | 65 | symbolize :kind, :in => [:im, :mobile, :email], :scopes => true 66 | 67 | 68 | Other examples: 69 | 70 | symbolize :os, :in => { 71 | :linux => "Linux", 72 | :mac => "Mac OS X" 73 | }, :scopes => true 74 | 75 | # Allow blank 76 | symbolize :gui, :in => [:gnome, :kde, :xfce], :allow_blank => true 77 | 78 | # Don`t i18n 79 | symbolize :browser, :in => [:firefox, :opera], :i18n => false, :methods => true 80 | 81 | # Scopes 82 | symbolize :angry, :in => [true, false], :scopes => true # AR 83 | symbolize :angry, :type => Boolean, :scopes => true # Mongoid 84 | 85 | # Don`t validate 86 | symbolize :lang, :in => [:ruby, :js, :c, :erlang], :validate => false 87 | 88 | # Default 89 | symbolize :kind, :in => [:admin, :manager, :user], :default => :user 90 | 91 | 92 | in/within 93 | --------- 94 | 95 | You can provide a hash like for values allowed on the field, e.g.: 96 | {:value => "Human text"} or an array of keys to run i18n on. 97 | Booleans are also supported. See below. 98 | 99 | 100 | Validate 101 | -------- 102 | 103 | Set to false to avoid the validation of the input. 104 | Useful for a dropdown with an "other" option textfield. 105 | 106 | symbolize :color, :in => [:green, :red, :blue], :validate => false 107 | 108 | There's is also allow_(blank|nil): As you expect. 109 | 110 | 111 | method 112 | ------ 113 | 114 | If you provide the method option, some fancy boolean methods will be added: 115 | In our User example, browser has this option, so you can do: 116 | 117 | @user.firefox? 118 | @user.opera? 119 | 120 | 121 | Booleans 122 | -------- 123 | 124 | Its possible to use boolean fields also. Looks better in Mongoid. 125 | 126 | # ActiveRecord 127 | symbolize :switch, :in => [true, false] 128 | 129 | # Mongoid 130 | symbolize :switch, :type => Boolean 131 | 132 | ... 133 | switch: 134 | "true": On 135 | "false": Off 136 | "nil": Unknown 137 | 138 | 139 | i18n 140 | ---- 141 | 142 | If you don`t provide a hash with values, it will try i18n: 143 | 144 | activerecord: 145 | or 146 | mongoid: 147 | symbolizes: 148 | user: 149 | gui: 150 | gnome: Gnome Desktop Enviroment 151 | kde: K Desktop Enviroment 152 | xfce: XFCE4 153 | gender: 154 | female: Girl 155 | male: Boy 156 | 157 | In some cases, if automatic lookup failes, you may want to try to translate it manully 158 | 159 | user.get_gender_text # here gender is symbolized field 160 | 161 | You can skip i18n lookup with :i18n => false 162 | 163 | symbolize :style, :in => [:rock, :punk, :funk, :jazz], :i18n => false 164 | 165 | 166 | Scopes 167 | ------ 168 | 169 | With the ':scopes => true' option, you may filter/read/write easily: 170 | 171 | User.gender(:female).each ... # => User.where({ :gender => :female }) 172 | 173 | 174 | Now, if you provide the ':scopes => :shallow' option, fancy named scopes 175 | will be added to the class directly. In our User example, gender has 176 | male/female options, so you can do: 177 | 178 | User.female.each ... # => User.where({ :gender => :female }) 179 | 180 | 181 | You can chain named scopes as well: 182 | 183 | User.female.mac => User.all :conditions => { :gender => :female, :so => :mac } 184 | 185 | For boolean colums you can use 186 | 187 | User.angry => User.find(:all, :conditions => { :angry => true }) 188 | User.not_angry => User.find(:all, :conditions => { :angry => false }) 189 | 190 | ( or with_[attribute] and without_[attribute] ) 191 | 192 | 193 | Default 194 | ------- 195 | 196 | As the name suggest, the symbol you choose as default will be set 197 | in new objects automatically. Mongoid only for now. 198 | 199 | symbolize :mood, :in => [:happy, :sad, :euphoric], :default => (MarvinDay ? :sad : :happy) 200 | 201 | User.new.mood # It may print :happy 202 | 203 | 204 | Rails Form Example 205 | ------------------ 206 | 207 | You may call `Class.get__values` anywhere to get a nice array. 208 | Works nice with dropdowns. Examples: 209 | 210 | class Coffee 211 | symbolize :genetic, :in => [:arabica, :robusta, :blend] 212 | end 213 | 214 | - form_for(@coffee) do |f| 215 | = f.label :genetic 216 | = f.select :genetic, Coffee.get_genetic_values 217 | 218 | Somewhere on a view: 219 | 220 | = select_tag :kind, Coffee.get_genetic_values 221 | 222 | Specs 223 | ----- 224 | 225 | Run the adapter independently: 226 | 227 | $ rspec spec/symbolize/mongoid_spec.rb 228 | $ rspec spec/symbolize/active_record_spec.rb 229 | 230 | 231 | Notes 232 | ----- 233 | 234 | This fork: 235 | http://github.com/nofxx/symbolize 236 | -------------------------------------------------------------------------------- /lib/symbolize/mongoid.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'active_support/core_ext/hash/keys' 3 | 4 | module Mongoid 5 | module Symbolize 6 | extend ActiveSupport::Concern 7 | 8 | # Symbolize Mongoid attributes. Add: 9 | # symbolize :attr_name 10 | # to your model class, to make an attribute return symbols instead of 11 | # string values. Setting such an attribute will accept symbols as well 12 | # as strings. 13 | # 14 | # There's no need for 'field :attr_name', symbolize will do it. 15 | # 16 | # Example: 17 | # class User 18 | # include Mongoid::Document 19 | # symbolize :gender, :in => [:female, :male] 20 | # symbolize :so, :in => { 21 | # :linux => "Linux", 22 | # :mac => "Mac OS X" 23 | # } 24 | # symbolize :gui, , :in => [:gnome, :kde, :xfce], :allow_blank => true 25 | # symbolize :browser, :in => [:firefox, :opera], :i18n => false 26 | # end 27 | # 28 | # It will automattically lookup for i18n: 29 | # 30 | # models: 31 | # symbolizes: 32 | # user: 33 | # gender: 34 | # female: Girl 35 | # male: Boy 36 | # 37 | # You can skip i18n lookup with :i18n => false 38 | # symbolize :gender, :in => [:female, :male], :i18n => false 39 | # 40 | # Its possible to use boolean fields also. 41 | # symbolize :switch, :in => [true, false] 42 | # 43 | # ... 44 | # switch: 45 | # "true": On 46 | # "false": Off 47 | # "nil": Unknown 48 | # 49 | module ClassMethods 50 | # Specifies that values of the given attributes should be returned 51 | # as symbols. The table column should be created of type string. 52 | 53 | def symbolize(*attr_names) # rubocop:disable Metrics/AbcSize 54 | configuration = attr_names.extract_options! 55 | configuration.assert_valid_keys(:in, :within, :i18n, :scopes, :methods, :capitalize, :validate, :default, :allow_blank, :allow_nil, :type) 56 | 57 | enum = configuration[:in] || configuration[:within] 58 | i18n = configuration[:i18n] 59 | i18n = enum && !enum.is_a?(Hash) if i18n.nil? 60 | scopes = configuration[:scopes] 61 | methods = configuration[:methods] 62 | capitalize = configuration[:capitalize] 63 | validation = configuration[:validate] != false 64 | default_option = configuration[:default] 65 | 66 | field_type = configuration[:type] || Symbol 67 | enum = [true, false] if [Boolean, ::Boolean].include?(field_type) 68 | 69 | attr_names.each do |attr_name| 70 | 71 | if enum # Enumerators 72 | enum_hash = \ 73 | if enum.is_a?(Hash) 74 | enum 75 | else # Maps [:a, :b, :c] -> {a: 'A', ... 76 | enum.each_with_object({}) do |e, a| 77 | a.store(e.respond_to?(:to_sym) ? e.to_sym : e, 78 | capitalize ? e.to_s.capitalize : e.to_s) 79 | end 80 | end 81 | 82 | # 83 | # Creates Mongoid's 'field :name, type: type, :default' 84 | # 85 | { :type => field_type }.tap do |field_opts| 86 | field_opts.merge!(:default => default_option) if default_option 87 | field attr_name, field_opts 88 | end 89 | 90 | # 91 | # Creates FIELD_VALUES constants 92 | # 93 | values_name = "#{attr_name}_values" 94 | values_const_name = values_name.upcase 95 | # Get the values of :in 96 | const_set values_const_name, enum_hash unless const_defined? values_const_name 97 | 98 | # 99 | # Define methods 100 | # 101 | ["get_#{values_name}", "#{attr_name}_enum"].each do |enum_method_name| 102 | define_singleton_method(enum_method_name) do 103 | if i18n 104 | enum_hash.each_key.map do |symbol| 105 | [i18n_translation_for(attr_name, symbol), symbol] 106 | end 107 | else 108 | enum_hash.map(&:reverse) 109 | end 110 | end 111 | end 112 | 113 | if methods 114 | enum_hash.each_key do |key| 115 | define_method("#{key}?") do 116 | send(attr_name) == key.to_sym 117 | end 118 | end 119 | end 120 | 121 | if scopes 122 | if scopes == :shallow 123 | enum_hash.each_key do |name| 124 | next unless name.respond_to?(:to_sym) 125 | scope name, -> { where(attr_name => name) } 126 | end 127 | else # scoped scopes 128 | scope attr_name, ->(val) { where(attr_name => val) } 129 | end 130 | end 131 | 132 | if validation 133 | validates(*attr_names, 134 | configuration.slice(:allow_nil, :allow_blank) 135 | .merge(:inclusion => { :in => enum_hash.keys })) 136 | end 137 | end 138 | 139 | # 140 | # Creates _text helper, human text for attribute. 141 | # 142 | define_method("#{attr_name}_text") do 143 | if i18n 144 | read_i18n_attribute(attr_name) 145 | else 146 | attr_value = send(attr_name) 147 | if enum 148 | enum_hash[attr_value] 149 | else 150 | attr_value.to_s 151 | end 152 | end 153 | end 154 | 155 | def i18n_translation_for(attr_name, attr_value) 156 | I18n.translate("mongoid.symbolizes.#{model_name.to_s.underscore}.#{attr_name}.#{attr_value}") 157 | end 158 | end 159 | end 160 | end # ClassMethods 161 | 162 | # Return an attribute's i18n 163 | def read_i18n_attribute(attr_name) 164 | t = self.class.i18n_translation_for(attr_name, read_attribute(attr_name)) 165 | t.is_a?(Hash) ? nil : t 166 | end 167 | end # Symbolize 168 | end # Mongoid 169 | 170 | # Symbolize::Mongoid = Mongoid::Symbolize 171 | -------------------------------------------------------------------------------- /lib/symbolize/active_record.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'active_support/core_ext/hash/keys' 3 | 4 | module Symbolize 5 | module ActiveRecord 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | # Returns an array of all the attributes that have been specified for symbolization 10 | class_attribute :symbolized_attributes, :instance_reader => false 11 | self.symbolized_attributes = [] 12 | end 13 | 14 | # Symbolize ActiveRecord attributes. Add 15 | # symbolize :attr_name 16 | # to your model class, to make an attribute return symbols instead of 17 | # string values. Setting such an attribute will accept symbols as well 18 | # as strings. In the database, the symbolized attribute should have 19 | # the column-type :string. 20 | # 21 | # Example: 22 | # class User < ActiveRecord::Base 23 | # symbolize :gender, :in => [:female, :male] 24 | # symbolize :so, :in => { 25 | # :linux => "Linux", 26 | # :mac => "Mac OS X" 27 | # } 28 | # symbolize :gui, , :in => [:gnome, :kde, :xfce], :allow_blank => true 29 | # symbolize :browser, :in => [:firefox, :opera], :i18n => false 30 | # end 31 | # 32 | # It will automattically lookup for i18n: 33 | # 34 | # activerecord: 35 | # symbolizes: 36 | # user: 37 | # gender: 38 | # female: Girl 39 | # male: Boy 40 | # 41 | # You can skip i18n lookup with :i18n => false 42 | # symbolize :gender, :in => [:female, :male], :i18n => false 43 | # 44 | # Its possible to use boolean fields also. 45 | # symbolize :switch, :in => [true, false] 46 | # 47 | # ... 48 | # switch: 49 | # "true": On 50 | # "false": Off 51 | # "nil": Unknown 52 | # 53 | module ClassMethods 54 | # Specifies that values of the given attributes should be returned 55 | # as symbols. The table column should be created of type string. 56 | 57 | def symbolize(*attr_names) 58 | configuration = attr_names.extract_options! 59 | configuration.assert_valid_keys(:in, :within, :i18n, :scopes, :methods, :capitalize, :validate, :default, :allow_blank, :allow_nil) 60 | 61 | enum = configuration[:in] || configuration[:within] 62 | i18n = configuration[:i18n] 63 | i18n = enum && !enum.is_a?(Hash) if i18n.nil? 64 | scopes = configuration[:scopes] 65 | methods = configuration[:methods] 66 | capitalize = configuration[:capitalize] 67 | validation = configuration[:validate] != false 68 | default_option = configuration[:default] 69 | 70 | attr_names.each do |attr_name| 71 | attr_name_str = attr_name.to_s 72 | 73 | if enum 74 | enum_hash = \ 75 | if enum.is_a?(Hash) 76 | enum 77 | else 78 | enum.each_with_object({}) do |e, a| 79 | a.store(e.respond_to?(:to_sym) ? e.to_sym : e, 80 | capitalize ? e.to_s.capitalize : e.to_s) 81 | end 82 | end 83 | 84 | values_name = attr_name_str + '_values' 85 | values_const_name = values_name.upcase 86 | 87 | # Get the values of :in 88 | const_set values_const_name, enum_hash unless const_defined? values_const_name 89 | 90 | [ 91 | 'get_' + values_name, 92 | attr_name_str + '_enum', 93 | ].each do |enum_method_name| 94 | 95 | define_singleton_method(enum_method_name) do 96 | if i18n 97 | enum_hash.each_key.map do |symbol| 98 | [i18n_translation_for(attr_name_str, symbol), symbol] 99 | end 100 | else 101 | enum_hash.map(&:reverse) 102 | end 103 | end 104 | end 105 | 106 | if methods 107 | enum_hash.each_key do |key| 108 | # It's a good idea to test for name collisions here and raise exceptions. 109 | # However, the existing software with this kind of errors will start crashing, 110 | # so I'd postpone this improvement until the next major version 111 | # this way it will not affect those people who use ~> in their Gemfiles 112 | 113 | # raise ArgumentError, "re-defined #{key}? method of #{self.name} class due to 'symbolize'" if method_defined?("#{key}?") 114 | 115 | define_method("#{key}?") do 116 | send(attr_name_str) == key.to_sym 117 | end 118 | end 119 | end 120 | 121 | if scopes 122 | if scopes == :shallow 123 | enum_hash.each_key do |name| 124 | next unless name.respond_to?(:to_sym) 125 | 126 | scope name, -> { where(attr_name_str => name) } 127 | # Figure out if this as another option, or default... 128 | # scope "not_#{name}", -> { where.not(attr_name_str => name) 129 | end 130 | else 131 | scope attr_name_str, ->(val) { where(attr_name_str => val) } 132 | end 133 | end 134 | 135 | if validation 136 | validates(*attr_names, configuration.slice(:allow_nil, :allow_blank).merge(:inclusion => { :in => enum_hash.keys })) 137 | end 138 | end 139 | 140 | define_method(attr_name_str) { read_and_symbolize_attribute(attr_name_str) || default_option } 141 | define_method(attr_name_str + '=') { |value| write_symbolized_attribute(attr_name_str, value) } 142 | 143 | if default_option 144 | before_save { self[attr_name_str] ||= default_option } 145 | else 146 | define_method(attr_name_str) { read_and_symbolize_attribute(attr_name_str) } 147 | end 148 | 149 | define_method(attr_name_str + '_text') do 150 | if i18n 151 | read_i18n_attribute(attr_name_str) 152 | else 153 | attr_value = send(attr_name_str) 154 | if enum 155 | enum_hash[attr_value] 156 | else 157 | attr_value.to_s 158 | end 159 | end 160 | end 161 | end 162 | 163 | # merge new symbolized attribute and create a new array to ensure that each class in inheritance hierarchy 164 | # has its own array of symbolized attributes 165 | self.symbolized_attributes += attr_names.map(&:to_s) 166 | end 167 | 168 | # Hook used by Rails to do extra stuff to attributes when they are initialized. 169 | def initialize_attributes(*args) 170 | super.tap do |attributes| 171 | # Make sure any default values read from the database are symbolized 172 | symbolized_attributes.each do |attr_name| 173 | attributes[attr_name] = symbolize_attribute(attributes[attr_name]) 174 | end 175 | end 176 | end 177 | 178 | # String becomes symbol, booleans string and nil nil. 179 | def symbolize_attribute(value) 180 | case value 181 | when String 182 | value.presence.try(:to_sym) 183 | when Symbol, TrueClass, FalseClass, Numeric 184 | value 185 | else 186 | nil 187 | end 188 | end 189 | 190 | def i18n_translation_for(attr_name, attr_value) 191 | I18n.translate("activerecord.symbolizes.#{model_name.to_s.underscore}.#{attr_name}.#{attr_value}") 192 | end 193 | end 194 | 195 | # String becomes symbol, booleans string and nil nil. 196 | def symbolize_attribute(value) 197 | self.class.symbolize_attribute(value) 198 | end 199 | 200 | # Return an attribute's value as a symbol or nil 201 | def read_and_symbolize_attribute(attr_name) 202 | symbolize_attribute(read_attribute(attr_name)) 203 | end 204 | 205 | # Return an attribute's i18n 206 | def read_i18n_attribute(attr_name) 207 | unless (t = self.class.i18n_translation_for(attr_name, read_attribute(attr_name))).is_a?(Hash) 208 | t 209 | end 210 | end 211 | 212 | # Write a symbolized value. Watch out for booleans. 213 | def write_symbolized_attribute(attr_name, value) 214 | write_attribute(attr_name, symbolize_attribute(value)) 215 | end 216 | end 217 | end 218 | -------------------------------------------------------------------------------- /spec/symbolize/mongoid_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'spec_helper' 3 | 4 | # 5 | # Test models 6 | class Person 7 | include Mongoid::Document 8 | include Mongoid::Symbolize 9 | include Mongoid::Timestamps 10 | 11 | symbolize :other, :i18n => false 12 | 13 | symbolize :language, :in => [:pt, :en] 14 | symbolize :sex, :type => Boolean, :scopes => true, :i18n => true, :allow_nil => true 15 | symbolize :status, :in => [:active, :inactive], :i18n => false, :capitalize => true, :scopes => :shallow 16 | symbolize :so, :allow_blank => true, :in => { 17 | :linux => 'Linux', 18 | :mac => 'Mac OS X', 19 | :win => 'Videogame', 20 | }, :scopes => true 21 | symbolize :gui, :allow_blank => true, :in => [:cocoa, :qt, :gtk], :i18n => false 22 | symbolize :karma, :in => %w(good bad ugly), :methods => true, :i18n => false, :allow_nil => true 23 | symbolize :planet, :in => %w(earth centauri tatooine), :default => :earth 24 | # symbolize :cool, :in => [true, false], :scopes => true 25 | 26 | symbolize :year, :in => Time.now.year.downto(1980).to_a, :validate => false 27 | 28 | has_many :rights, :dependent => :destroy 29 | has_many :extras, :dependent => :destroy, :class_name => 'PersonExtra' 30 | embeds_many :skills, :class_name => 'PersonSkill' 31 | end 32 | 33 | class PersonSkill 34 | include Mongoid::Document 35 | include Mongoid::Symbolize 36 | embedded_in :person, :inverse_of => :skills 37 | 38 | symbolize :kind, :in => [:agility, :magic] 39 | end 40 | 41 | class PersonExtra 42 | include Mongoid::Document 43 | include Mongoid::Symbolize 44 | belongs_to :person, :inverse_of => :extras 45 | 46 | symbolize :key, :in => [:one, :another] 47 | end 48 | 49 | class Right 50 | include Mongoid::Document 51 | include Mongoid::Symbolize 52 | 53 | validates :name, :presence => true 54 | symbolize :kind, :in => [:temp, :perm], :default => :perm 55 | end 56 | 57 | class Project 58 | include Mongoid::Document 59 | 60 | field :name 61 | field :state, :default => 'active' 62 | 63 | # Comment 1 line and it works, both fails: 64 | default_scope -> { where(:state => 'active') } 65 | scope :inactive, -> { any_in(:state => [:done, :wip]) } 66 | scope :dead, -> { all_of(:state => :wip, :name => 'zim') } 67 | end 68 | 69 | describe 'Symbolize' do 70 | 71 | it 'should be a module' do 72 | expect(Mongoid.const_defined?('Symbolize')).to be true 73 | end 74 | 75 | it 'should instantiate' do 76 | anna = Person.create(:name => 'Anna', :so => :mac, :gui => :cocoa, :language => :pt, :status => :active, :sex => true) 77 | # anna.should be_valid 78 | expect(anna.errors.messages).to eql({}) 79 | end 80 | 81 | describe 'Person Instantiated' do 82 | let(:person) do 83 | Person.create(:name => 'Anna', :other => :fo, :status => :active, 84 | :so => :linux, :gui => :qt, :language => :pt, 85 | :sex => true, :cool => true) 86 | end 87 | 88 | it 'should work nice with strings' do 89 | person.status = 'inactive' 90 | expect(person.status).to eql(:inactive) 91 | expect(person.read_attribute(:status)).to eql(:inactive) 92 | end 93 | 94 | it 'should obviously work nice with symbols, we like symbols' do 95 | person.status = :active 96 | expect(person.status).to eql(:active) 97 | person.save 98 | expect(person.status).to eql(:active) 99 | expect(person[:status]).to eql(:active) 100 | end 101 | 102 | it 'should make strings symbols from initializer' do 103 | other = Person.new(:language => 'en', :so => :mac, :gui => :cocoa, :language => :pt, :sex => true, :status => 'active') 104 | expect(other[:status]).to eq(:active) 105 | expect(other.status).to eq(:active) 106 | end 107 | 108 | it 'should symbolize obj#attributes["foo"]' do 109 | person = Person.create!(:language => 'en', :status => :active) 110 | expect(person.attributes['language']).to eq(:en) 111 | expect(Person.first.attributes['language']).to eq(:en) 112 | end 113 | 114 | # it "should work nice with numbers" do 115 | # pending 116 | # person.status = 43 117 | # person.status.should_not be_nil 118 | # # person.status_before_type_cast.should be_nil 119 | # # person.read_attribute(:status).should be_nil 120 | # end 121 | 122 | it 'should acts nice with nil when reading' do 123 | person.karma = nil 124 | expect(person.karma).to be_nil 125 | person.save 126 | expect(person.read_attribute(:karma)).to be_nil 127 | end 128 | 129 | it 'should acts nice with nil #symbol_text' do 130 | person.karma = nil 131 | expect(person.karma).to be_nil 132 | person.save 133 | expect(person.karma_text).to be_nil 134 | end 135 | 136 | it 'should acts nice with blank when reading' do 137 | person.so = '' 138 | expect(person.so).to be_blank 139 | person.save 140 | expect(person.read_attribute(:so)).to be_blank 141 | end 142 | 143 | it 'should acts nice with blank #symbol_text' do 144 | person.so = '' 145 | person.save 146 | expect(person.so_text).to be_nil 147 | end 148 | 149 | it 'should recognize 0 as false on boolean fields' do 150 | person.update_attribute(:sex, '0') 151 | expect(person.sex).to eq(false) 152 | end 153 | 154 | it 'should recognize 1 as true on boolean fields' do 155 | person.update_attribute(:sex, '1') 156 | expect(person.sex).to eq(true) 157 | end 158 | 159 | it 'should not validates other' do 160 | person.other = nil 161 | expect(person).to be_valid 162 | person.other = '' 163 | expect(person).to be_valid 164 | end 165 | 166 | it 'should get the correct values' do 167 | expect(Person.get_status_values).to eql([['Active', :active], ['Inactive', :inactive]]) 168 | expect(Person::STATUS_VALUES).to eql(:inactive => 'Inactive', :active => 'Active') 169 | end 170 | 171 | it 'should get the values for RailsAdmin' do 172 | expect(Person.status_enum).to eql([['Active', :active], ['Inactive', :inactive]]) 173 | end 174 | 175 | it 'should have a human _text method' do 176 | expect(person.status_text).to eql('Active') 177 | end 178 | 179 | it 'should work nice with i18n' do 180 | expect(person.language_text).to eql('Português') 181 | end 182 | 183 | it 'test_symbolize_humanize' do 184 | expect(person.status_text).to eql('Active') 185 | end 186 | 187 | it 'should get the correct values' do 188 | expect(Person.get_gui_values).to match_array([['cocoa', :cocoa], ['qt', :qt], ['gtk', :gtk]]) 189 | expect(Person::GUI_VALUES).to eql(:cocoa => 'cocoa', :qt => 'qt', :gtk => 'gtk') 190 | end 191 | 192 | it 'test_symbolize_humanize' do 193 | expect(person.gui_text).to eql('qt') 194 | end 195 | 196 | it 'should get the correct values' do 197 | expect(Person.get_so_values).to match_array([['Linux', :linux], ['Mac OS X', :mac], ['Videogame', :win]]) 198 | expect(Person::SO_VALUES).to eql(:linux => 'Linux', :mac => 'Mac OS X', :win => 'Videogame') 199 | end 200 | 201 | it 'test_symbolize_humanize' do 202 | expect(person.so_text).to eql('Linux') 203 | end 204 | 205 | it 'test_symbolize_humanize' do 206 | person.so = :mac 207 | expect(person.so_text).to eql('Mac OS X') 208 | end 209 | 210 | it 'should stringify' do 211 | expect(person.other_text).to eql('fo') 212 | person.other = :foo 213 | expect(person.other_text).to eql('foo') 214 | end 215 | 216 | it 'should work with weird chars' do 217 | person.status = :"weird'; chars" 218 | expect(person.status).to eql(:"weird'; chars") 219 | end 220 | 221 | it 'should work fine through relations' do 222 | person.extras.create(:key => :one) 223 | expect(PersonExtra.first.key).to eql(:one) 224 | end 225 | 226 | it 'should work fine through embeds' do 227 | person.skills.create(:kind => :magic) 228 | expect(person.skills.first.kind).to eql(:magic) 229 | end 230 | 231 | it 'should default planet to earth' do 232 | expect(Person.new.planet).to eql(:earth) 233 | end 234 | 235 | describe 'validation' do 236 | 237 | it 'should validate from initializer' do 238 | other = Person.new(:language => 'en', :so => :mac, :gui => :cocoa, :language => :pt, :sex => true, :status => 'active') 239 | expect(other).to be_valid 240 | expect(other.errors.messages).to eq({}) 241 | end 242 | 243 | it 'should validate nil' do 244 | person.status = nil 245 | expect(person).not_to be_valid 246 | expect(person.errors.messages).to have_key(:status) 247 | end 248 | 249 | it 'should validate not included' do 250 | person.language = 'xx' 251 | expect(person).not_to be_valid 252 | expect(person.errors.messages).to have_key(:language) 253 | end 254 | 255 | it 'should not validate so' do 256 | person.so = nil 257 | expect(person).to be_valid 258 | end 259 | 260 | it 'should validate ok' do 261 | person.language = 'pt' 262 | expect(person).to be_valid 263 | expect(person.errors.messages).to eq({}) 264 | end 265 | 266 | end 267 | 268 | describe 'i18n' do 269 | 270 | it 'should test i18n ones' do 271 | expect(person.language_text).to eql('Português') 272 | end 273 | 274 | it 'should get the correct values' do 275 | expect(Person.get_language_values).to match_array([['Português', :pt], ['Inglês', :en]]) 276 | end 277 | 278 | it 'should get the correct values' do 279 | expect(Person::LANGUAGE_VALUES).to eql(:pt => 'pt', :en => 'en') 280 | end 281 | 282 | it 'should test boolean' do 283 | expect(person.sex_text).to eql('Feminino') 284 | end 285 | 286 | it 'should get the correct values' do 287 | expect(Person.get_sex_values).to eql([['Feminino', true], ['Masculino', false]]) 288 | end 289 | 290 | it 'should get the correct values' do 291 | expect(Person::SEX_VALUES).to eql(true => 'true', false => 'false') 292 | end 293 | 294 | it 'should translate a multiword classname' do 295 | skill = PersonSkill.new(:kind => :magic) 296 | expect(skill.kind_text).to eql('Mágica') 297 | end 298 | 299 | it "should return nil if there's no value" do 300 | skill = PersonSkill.new(:kind => nil) 301 | expect(skill.kind_text).to be_nil 302 | end 303 | 304 | it "should return the proper 'false' i18n if the attr value is false" do 305 | person = Person.new(:sex => false) 306 | expect(person.sex_text).to eq('Masculino') 307 | end 308 | 309 | it 'should use i18n if i18n => true' do 310 | expect(person.sex_text).to eql('Feminino') 311 | end 312 | 313 | end 314 | 315 | describe 'Methods' do 316 | 317 | it 'should play nice with other stuff' do 318 | expect(person.karma).to be_nil 319 | expect(Person::KARMA_VALUES).to eql(:bad => 'bad', :ugly => 'ugly', :good => 'good') 320 | end 321 | 322 | it 'should provide a boolean method' do 323 | expect(person).not_to be_good 324 | person.karma = :ugly 325 | expect(person).to be_ugly 326 | end 327 | 328 | it 'should work' do 329 | person.karma = 'good' 330 | expect(person).to be_good 331 | expect(person).not_to be_bad 332 | end 333 | 334 | end 335 | 336 | end 337 | 338 | describe 'more tests on Right' do 339 | 340 | it 'should not interfer on create' do 341 | Right.create!(:name => 'p7', :kind => :temp) 342 | expect(Right.where(:name => 'p7').first.kind).to eql(:temp) 343 | end 344 | 345 | it 'should work on create' do 346 | pm = Right.new(:name => 'p7') 347 | expect(pm).to be_valid 348 | expect(pm.save).to be true 349 | end 350 | 351 | it 'should work on create' do 352 | Right.create(:name => 'p8') 353 | expect(Right.find_by(:name => 'p8').kind).to eql(:perm) 354 | end 355 | 356 | it 'should work on edit' do 357 | Right.create(:name => 'p8') 358 | pm = Right.where(:name => 'p8').first 359 | pm.kind = :temp 360 | pm.save 361 | expect(Right.where(:name => 'p8').first.kind).to eql(:temp) 362 | end 363 | 364 | end 365 | 366 | describe 'Default Values' do 367 | 368 | it 'should use default value on object build' do 369 | expect(Right.new.kind).to eql(:perm) 370 | end 371 | 372 | it 'should use default value in string' do 373 | expect(Project.new.state).to eql('active') 374 | end 375 | 376 | end 377 | 378 | describe 'Scopes' do 379 | it 'should work under scope' do 380 | # Person.with_scope({ :status => :inactive }) do 381 | # Person.all.map(&:name).should eql(['Bob']) 382 | # end 383 | end 384 | 385 | it 'should work under scope' do 386 | Project.create(:name => 'A', :state => :done) 387 | Project.create(:name => 'B', :state => :active) 388 | expect(Project.count).to eql(1) 389 | end 390 | 391 | it 'should now shallow scoped scopes' do 392 | Person.create(:name => 'Bob', :other => :bar, :status => :active, :so => :linux, :gui => :gtk, :language => :en, :sex => false, :cool => false) 393 | expect(Person).not_to respond_to(:status) 394 | end 395 | 396 | it 'should set some scopes' do 397 | Person.create(:name => 'Bob', :other => :bar, :status => :active, :so => :linux, :gui => :gtk, :language => :en, :sex => false, :cool => false) 398 | expect(Person.so(:linux)).to be_a(Mongoid::Criteria) 399 | expect(Person.so(:linux).count).to eq(1) 400 | end 401 | 402 | it 'should work with a shallow scope too' do 403 | Person.create!(:name => 'Bob', :other => :bar, :status => :active, :so => :linux, :gui => :gtk, :language => :en, :sex => false, :cool => false) 404 | expect(Person.active).to be_a(Mongoid::Criteria) 405 | expect(Person.active.count).to eq(1) 406 | end 407 | 408 | end 409 | 410 | describe 'Mongoid stuff' do 411 | 412 | it 'test_symbolized_finder' do 413 | Person.create(:name => 'Bob', :other => :bar, :status => :inactive, :so => :mac, :gui => :gtk, :language => :en, :sex => false, :cool => false) 414 | 415 | expect(Person.where(:status => :inactive).all.map(&:name)).to eql(['Bob']) 416 | expect(Person.where(:status => :inactive).map(&:name)).to eql(['Bob']) 417 | end 418 | 419 | describe 'dirty tracking / changed flag' do 420 | 421 | before do 422 | Person.create!(:name => 'Anna', :other => :fo, :status => :active, 423 | :so => :linux, :gui => :qt, :language => :pt, 424 | :sex => true, :cool => true) 425 | @anna = Person.where(:name => 'Anna').first 426 | end 427 | 428 | it 'is dirty if you change the attribute value' do 429 | expect(@anna.language).to eq(:pt) 430 | expect(@anna.language_changed?).to be false 431 | 432 | return_value = @anna.language = :en 433 | expect(return_value).to eq(:en) 434 | expect(@anna.language_changed?).to be true 435 | end 436 | 437 | it 'is dirty if you change the attribute value' do 438 | @anna.language = :en 439 | expect(@anna.changed).to be_empty 440 | end 441 | 442 | it 'is not dirty if you set the attribute value to the same value' do 443 | return_value = @anna.language = :pt 444 | expect(return_value).to eq(:pt) 445 | expect(@anna.language_changed?).to be false 446 | end 447 | 448 | it 'is not dirty if you set the same value as string instead of symbol' do 449 | @anna.language = 'pt' 450 | expect(@anna.language).to eq(:pt) 451 | expect(@anna.language_changed?).to be false 452 | end 453 | 454 | end 455 | 456 | end 457 | 458 | end 459 | -------------------------------------------------------------------------------- /spec/symbolize/active_record_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'spec_helper' 3 | 4 | # 5 | # Test model 6 | class User < ActiveRecord::Base 7 | symbolize :other 8 | symbolize :language, :in => [:pt, :en] 9 | symbolize :sex, :in => [true, false], :scopes => true, :allow_nil => true 10 | symbolize :status, :in => [:active, :inactive], :i18n => false, :capitalize => true, :scopes => :shallow, :methods => true 11 | symbolize :so, :allow_blank => true, :in => { 12 | :linux => 'Linux', 13 | :mac => 'Mac OS X', 14 | :win => 'Videogame', 15 | }, :scopes => true 16 | symbolize :gui, :allow_blank => true, :in => [:cocoa, :qt, :gtk], :i18n => false 17 | symbolize :karma, :in => %w(good bad ugly), :methods => true, :i18n => false, :allow_nil => true 18 | symbolize :cool, :in => [true, false], :scopes => true 19 | 20 | symbolize :role, :in => [:reader, :writer, :some_existing_attr], :i18n => false, :methods => true, :default => :reader 21 | symbolize :country, :in => [:us, :gb, :pt, :ru], :capitalize => true, :i18n => false # note: the default value is provided in db migration 22 | 23 | has_many :extras, :dependent => :destroy, :class_name => 'UserExtra' 24 | has_many :access, :dependent => :destroy, :class_name => 'UserAccess' 25 | end 26 | 27 | class UserSkill < ActiveRecord::Base 28 | symbolize :kind, :in => [:agility, :magic] 29 | end 30 | 31 | class UserExtra < ActiveRecord::Base 32 | symbolize :key, :in => [:one, :another] 33 | end 34 | 35 | class Permission < ActiveRecord::Base 36 | validates_presence_of :name 37 | symbolize :kind, :in => [:temp, :perm], :default => :perm 38 | symbolize :lvl, :in => (1..9).to_a, :i18n => false # , :default => 1 39 | end 40 | 41 | class PermissionSubclass < Permission 42 | symbolize :sub_lvl 43 | end 44 | 45 | describe 'Symbolize' do 46 | before do 47 | [User, Permission].each(&:delete_all) 48 | end 49 | 50 | it 'should respond to symbolize' do 51 | expect(ActiveRecord::Base).to respond_to :symbolize 52 | end 53 | 54 | it 'should have a valid blueprint' do 55 | # Test records 56 | u = User.create(:name => 'Bob', :other => :bar, :status => :inactive, 57 | :so => :mac, :gui => :gtk, :language => :en, :cool => false) 58 | expect(u.errors.messages).to be_blank 59 | end 60 | 61 | it 'should work nice with default values from active model' do 62 | u = User.create(:name => 'Niu', :other => :bar, :so => :mac, 63 | :language => :en, :cool => false) 64 | expect(u.errors.messages).to be_blank 65 | expect(u.status).to eql(:active) 66 | expect(u).to be_active 67 | end 68 | 69 | describe '.symbolized_attributes' do 70 | it 'returns the symbolized attribute for the class' do 71 | expect(UserExtra.symbolized_attributes).to eq ['key'] 72 | expect(Permission.symbolized_attributes).to match_array %w(kind lvl) 73 | expect(PermissionSubclass.symbolized_attributes).to match_array %w(kind lvl sub_lvl) 74 | end 75 | end 76 | 77 | describe 'User Instantiated' do 78 | subject do 79 | User.create(:name => 'Anna', :other => :fo, :status => status, :so => so, 80 | :gui => :qt, :language => 'pt', :sex => true, :cool => true) 81 | end 82 | let(:status) { :active } 83 | let(:so) { :linux } 84 | 85 | describe 'test_symbolize_string' do 86 | let(:status) { 'inactive' } 87 | 88 | describe '#status' do 89 | subject { super().status } 90 | it { is_expected.to eq(:inactive) } 91 | end 92 | # @user.status_before_type_cast.should eql(:inactive) 93 | # @user.read_attribute(:status).should eql('inactive') 94 | end 95 | 96 | describe 'test_symbolize_symbol' do 97 | describe '#status' do 98 | subject { super().status } 99 | it { is_expected.to eq(:active) } 100 | end 101 | 102 | describe '#status_before_type_cast' do 103 | subject { super().status_before_type_cast } 104 | it { is_expected.to eq(:active) } 105 | end 106 | 107 | describe '#status_before_type_cast' do 108 | subject { super().attributes['language'] } 109 | it { is_expected.to eq(:pt) } 110 | end 111 | end 112 | 113 | describe 'should work nice with numbers' do 114 | let(:status) { 43 } 115 | 116 | describe '#status' do 117 | subject { super().status } 118 | it { is_expected.to be_present } 119 | end 120 | # @user.status_before_type_cast.should be_nil 121 | # @user.read_attribute(:status).should be_nil 122 | end 123 | 124 | describe 'should acts nice with nil' do 125 | let(:status) { nil } 126 | 127 | describe '#status' do 128 | subject { super().status } 129 | it { is_expected.to be_nil } 130 | end 131 | 132 | describe '#status_before_type_cast' do 133 | subject { super().status_before_type_cast } 134 | it { is_expected.to be_nil } 135 | end 136 | it { expect(subject.read_attribute(:status)).to be_nil } 137 | end 138 | 139 | describe 'should acts nice with blank' do 140 | let(:status) { '' } 141 | 142 | describe '#status' do 143 | subject { super().status } 144 | it { is_expected.to be_nil } 145 | end 146 | 147 | describe '#status_before_type_cast' do 148 | subject { super().status_before_type_cast } 149 | it { is_expected.to be_nil } 150 | end 151 | it { expect(subject.read_attribute(:status)).to be_nil } 152 | end 153 | 154 | it 'should not validates other' do 155 | subject.other = nil 156 | expect(subject).to be_valid 157 | subject.other = '' 158 | expect(subject).to be_valid 159 | end 160 | 161 | it 'should get the correct values' do 162 | expect(User.get_status_values).to eql([['Active', :active], ['Inactive', :inactive]]) 163 | expect(User::STATUS_VALUES).to eql(:inactive => 'Inactive', :active => 'Active') 164 | end 165 | 166 | it 'should get the values for RailsAdmin' do 167 | expect(User.status_enum).to eql([['Active', :active], ['Inactive', :inactive]]) 168 | end 169 | 170 | describe 'test_symbolize_humanize' do 171 | describe '#status_text' do 172 | subject { super().status_text } 173 | it { is_expected.to eql('Active') } 174 | end 175 | end 176 | 177 | it 'should get the correct values' do 178 | expect(User.get_gui_values).to match_array([['cocoa', :cocoa], ['qt', :qt], ['gtk', :gtk]]) 179 | expect(User::GUI_VALUES).to eql(:cocoa => 'cocoa', :qt => 'qt', :gtk => 'gtk') 180 | end 181 | 182 | describe 'test_symbolize_humanize' do 183 | describe '#gui_text' do 184 | subject { super().gui_text } 185 | it { is_expected.to eql('qt') } 186 | end 187 | end 188 | 189 | it 'should get the correct values' do 190 | expect(User.get_so_values).to match_array([['Linux', :linux], ['Mac OS X', :mac], ['Videogame', :win]]) 191 | expect(User::SO_VALUES).to eql(:linux => 'Linux', :mac => 'Mac OS X', :win => 'Videogame') 192 | end 193 | 194 | describe 'test_symbolize_humanize' do 195 | describe '#so_text' do 196 | subject { super().so_text } 197 | it { is_expected.to eql('Linux') } 198 | end 199 | end 200 | 201 | describe 'test_symbolize_humanize' do 202 | let(:so) { :mac } 203 | 204 | describe '#so_text' do 205 | subject { super().so_text } 206 | it { is_expected.to eql('Mac OS X') } 207 | end 208 | end 209 | 210 | it 'should stringify' do 211 | expect(subject.other_text).to eql('fo') 212 | subject.other = :foo 213 | expect(subject.other_text).to eql('foo') 214 | end 215 | 216 | describe 'should validate status' do 217 | let(:status) { nil } 218 | it { is_expected.not_to be_valid } 219 | it 'has 1 error' do 220 | expect(subject.errors.size).to eq(1) 221 | end 222 | end 223 | 224 | it 'should not validate so' do 225 | subject.so = nil 226 | expect(subject).to be_valid 227 | end 228 | 229 | it 'test_symbols_with_weird_chars_quoted_id' do 230 | subject.status = :"weird'; chars" 231 | expect(subject.status_before_type_cast).to eql(:"weird'; chars") 232 | end 233 | 234 | it 'should work fine through relations' do 235 | subject.extras.create(:key => :one) 236 | expect(UserExtra.first.key).to eql(:one) 237 | end 238 | 239 | it 'should play fine with null db columns' do 240 | new_extra = subject.extras.build 241 | expect(new_extra).not_to be_valid 242 | end 243 | 244 | it 'should play fine with null db columns' do 245 | new_extra = subject.extras.build 246 | expect(new_extra).not_to be_valid 247 | end 248 | 249 | describe 'i18n' do 250 | 251 | it 'should test i18n ones' do 252 | expect(subject.language_text).to eql('Português') 253 | end 254 | 255 | it 'should get the correct values' do 256 | expect(User.get_language_values).to match_array([['Português', :pt], ['Inglês', :en]]) 257 | end 258 | 259 | it 'should get the correct values' do 260 | expect(User::LANGUAGE_VALUES).to eql(:pt => 'pt', :en => 'en') 261 | end 262 | 263 | it 'should test boolean' do 264 | expect(subject.sex_text).to eql('Feminino') 265 | subject.sex = false 266 | expect(subject.sex_text).to eql('Masculino') 267 | end 268 | 269 | it 'should recognize 0 as false on boolean fields' do 270 | subject.update_attribute(:sex, '0') 271 | expect(subject.sex).to eq(false) 272 | end 273 | 274 | it 'should recognize 1 as true on boolean fields' do 275 | subject.update_attribute(:sex, '1') 276 | expect(subject.sex).to eq(true) 277 | end 278 | 279 | it 'should get the correct values' do 280 | expect(User.get_sex_values).to eql([['Feminino', true], ['Masculino', false]]) 281 | end 282 | 283 | it 'should get the correct values' do 284 | expect(User::SEX_VALUES).to eql(true => 'true', false => 'false') 285 | end 286 | 287 | it 'should translate a multiword class' do 288 | @skill = UserSkill.create(:kind => :magic) 289 | expect(@skill.kind_text).to eql('Mágica') 290 | end 291 | 292 | it "should return nil if there's no value" do 293 | @skill = UserSkill.create(:kind => nil) 294 | expect(@skill.kind_text).to be_nil 295 | end 296 | 297 | end 298 | 299 | describe 'Methods' do 300 | 301 | it 'should play nice with other stuff' do 302 | expect(subject.karma).to be_nil 303 | expect(User::KARMA_VALUES).to eql(:bad => 'bad', :ugly => 'ugly', :good => 'good') 304 | end 305 | 306 | it 'should provide a boolean method' do 307 | expect(subject).not_to be_good 308 | subject.karma = :ugly 309 | expect(subject).to be_ugly 310 | end 311 | 312 | it 'should work' do 313 | subject.karma = 'good' 314 | expect(subject).to be_good 315 | expect(subject).not_to be_bad 316 | end 317 | 318 | end 319 | 320 | describe 'Changes' do 321 | 322 | it 'is dirty if you change the attribute value' do 323 | expect(subject.language).to eq(:pt) 324 | expect(subject.language_changed?).to be false 325 | 326 | return_value = subject.language = :en 327 | expect(return_value).to eq(:en) 328 | expect(subject.language_changed?).to be true 329 | end 330 | 331 | it 'is not dirty if you set the attribute value to the same value' do 332 | expect(subject.language).to eq(:pt) 333 | expect(subject.language_changed?).to be false 334 | 335 | return_value = subject.language = :pt 336 | expect(return_value).to eq(:pt) 337 | expect(subject.language_changed?).to be false 338 | end 339 | 340 | it 'is not changed if you set the attribute value to the same value' do 341 | subject.language = :pt 342 | expect(subject.changed).to be_empty 343 | end 344 | 345 | it 'is not dirty if you set the value to the same but as string' do 346 | expect(subject.language).to eq(:pt) 347 | expect(subject.language_changed?).to be false 348 | 349 | subject.language = 'pt' 350 | expect(subject.language_changed?).to be false 351 | end 352 | 353 | it 'is not dirty if you set the default attribute value to the same value' do 354 | user = User.create!(:language => :pt, :sex => true, :cool => true) 355 | expect(user.status).to eq(:active) 356 | expect(user).not_to be_changed 357 | 358 | user.status = :active 359 | expect(user).not_to be_changed 360 | end 361 | 362 | it 'is not dirty if you set the default attribute value to the same value (string)' do 363 | user = User.create!(:language => :pt, :sex => true, :cool => true) 364 | expect(user.status).to eq(:active) 365 | expect(user).not_to be_changed 366 | 367 | user.status = 'active' 368 | expect(user).not_to be_changed 369 | end 370 | end 371 | 372 | end 373 | 374 | describe 'more tests on Permission' do 375 | 376 | it 'should use default value on object build' do 377 | expect(Permission.new.kind).to eql(:perm) 378 | end 379 | 380 | it 'should not interfer on create' do 381 | Permission.create!(:name => 'p7', :kind => :temp, :lvl => 7) 382 | expect(Permission.find_by_name('p7').kind).to eql(:temp) 383 | end 384 | 385 | it 'should work on create' do 386 | pm = Permission.new(:name => 'p7', :lvl => 7) 387 | expect(pm).to be_valid 388 | expect(pm.save).to be true 389 | end 390 | 391 | it 'should work on create' do 392 | Permission.create!(:name => 'p8', :lvl => 9) 393 | expect(Permission.find_by_name('p8').kind).to eql(:perm) 394 | end 395 | 396 | it 'should work on edit' do 397 | Permission.create!(:name => 'p8', :lvl => 9) 398 | pm = Permission.find_by_name('p8') 399 | pm.kind = :temp 400 | pm.save 401 | expect(Permission.find_by_name('p8').kind).to eql(:temp) 402 | end 403 | 404 | it 'should work with default values' do 405 | pm = Permission.new(:name => 'p9') 406 | pm.lvl = 9 407 | pm.save 408 | expect(Permission.find_by_name('p9').lvl.to_i).to eql(9) 409 | end 410 | 411 | it 'should work with attributes' do 412 | Permission.create!(:name => 'p88', :kind => 'temp', :lvl => 8) 413 | expect(Permission.find_by_name('p88').kind).to eql(:temp) 414 | end 415 | 416 | end 417 | 418 | describe 'Named Scopes' do 419 | 420 | before do 421 | @anna = User.create(:name => 'Anna', :other => :fo, :status => :active, :so => :linux, :gui => :qt, :language => :pt, :sex => true, :cool => true) 422 | @mary = User.create(:name => 'Mary', :other => :fo, :status => :inactive, :so => :mac, :language => :pt, :sex => true, :cool => true) 423 | end 424 | 425 | it 'test_symbolized_finder' do 426 | expect(User.where(:status => :inactive).all.map(&:name)).to eql(['Mary']) 427 | end 428 | 429 | it 'test_symbolized_scoping' do 430 | User.where(:status => :inactive).scoping do 431 | expect(User.all.map(&:name)).to eql(['Mary']) 432 | end 433 | end 434 | 435 | it 'should have main named scope' do 436 | expect(User.inactive).to eq([@mary]) 437 | end 438 | 439 | it 'should have other to test better' do 440 | expect(User.so(:linux)).to eq([@anna]) 441 | end 442 | 443 | # it "should have 'with' helper" do 444 | # User.with_sex.should == [@anna] 445 | # end 446 | 447 | # it "should have 'without' helper" do 448 | # User.without_sex.should == [@bob] 449 | # end 450 | 451 | # it "should have 'attr_name' helper" do 452 | # User.cool.should == [@anna] 453 | # end 454 | 455 | # it "should have 'not_attr_name' helper" do 456 | # User.not_cool.should == [@bob] 457 | # end 458 | 459 | end 460 | 461 | describe ': Default Value' do 462 | before(:each) do 463 | @user = User.new(:name => 'Anna', :other => :fo, :status => :active, :so => :linux, :gui => :qt, :language => :pt, :sex => true, :cool => true) 464 | end 465 | 466 | it 'should be considered during validation' do 467 | @user.valid? 468 | expect(@user.errors.full_messages).to eq([]) 469 | end 470 | 471 | it 'should be taken from the DB schema definition' do 472 | expect(@user.country).to eq(:pt) 473 | expect(@user.country_text).to eq('Pt') 474 | end 475 | 476 | it 'should be applied to new, just saved, and reloaded objects, and also play fine with :methods option' do 477 | expect(@user.role).to eq(:reader) 478 | expect(@user.role_text).to eq('reader') 479 | expect(@user).to be_reader 480 | @user.save! 481 | expect(@user.role).to eq(:reader) 482 | expect(@user).to be_reader 483 | @user.reload 484 | expect(@user.role).to eq(:reader) 485 | expect(@user).to be_reader 486 | end 487 | 488 | it 'should be overridable' do 489 | @user.role = :writer 490 | expect(@user.role).to eq(:writer) 491 | expect(@user).to be_writer 492 | @user.save! 493 | expect(@user.role).to eq(:writer) 494 | expect(@user).to be_writer 495 | @user.reload 496 | expect(@user.role).to eq(:writer) 497 | expect(@user).to be_writer 498 | end 499 | 500 | # This feature is for the next major version (b/o the compatibility problem) 501 | it "should detect name collision caused by ':methods => true' option" do 502 | pending 'next major version' 503 | expect do 504 | User.class_eval do 505 | # 'reader?' method is already defined, so the line below should raise an error 506 | symbolize :some_attr, :in => [:reader, :guest], :methods => true 507 | end 508 | end.to raise_error(ArgumentError) 509 | end 510 | 511 | end 512 | end 513 | --------------------------------------------------------------------------------