├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── token_phrase.rb └── token_phrase │ ├── dictionary.rb │ ├── generator.rb │ └── version.rb ├── test ├── generator_test.rb └── test_helper.rb └── token_phrase.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | - 2.0.0 7 | - rbx-19mode 8 | - jruby-19mode 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'backports', :require => false 6 | 7 | gemspec 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Eric Steele 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TokenPhrase (v1.0.6) 2 | [![Build Status](https://travis-ci.org/genericsteele/token_phrase.png?branch=master)](https://travis-ci.org/genericsteele/token_phrase) 3 | 4 | TokenPhrase is a simple gem that generates unique phrases for you to use in your app as tokens. 5 | 6 | "Why?" you may be asking. Why not? Token phrases give your app a little personality and make support a lot easier. 7 | 8 | ## Installation 9 | 10 | Add this line to your application's Gemfile: 11 | 12 | gem 'token_phrase' 13 | 14 | And then execute: 15 | 16 | $ bundle 17 | 18 | Or install it yourself as: 19 | 20 | $ gem install token_phrase 21 | 22 | ## TokenPhrase.generate(separator = nil, dictionaries = {}) 23 | 24 | ### Defaults 25 | By default, TokenPhrase uses the included dictionaries, separates everything with a hyphen (-), and appends a random number between 1 and 1,000,000 : 26 | 27 | ```ruby 28 | TokenPhrase.generate 29 | => "groovy-red-fractal-fork-101589" 30 | 31 | 5.times { p TokenPhrase.generate } 32 | "bodacious-blue-spotted-envelope-428491" 33 | "magnetic-burnt-orange-polka-dotted-spider-wolf-268974" 34 | "conservative-plum-paisley-banana-slug-771632" 35 | "bluetooth-chiffon-houndstooth-wolf-spider-700306" 36 | "sweet-violet-tartan-coyote-16101" 37 | ``` 38 | 39 | With the current dictionaries and numbers, there are 4,199,040,000,000 (four trillion!) unique possibilites. 40 | 41 | ### Separators 42 | If you would like a different separator, just pass a string as an argument: 43 | 44 | ```ruby 45 | TokenPhrase.generate('$') 46 | => "groovy$salmon$paisley$cape$848536" 47 | 48 | TokenPhrase.generate('MARSIPAN') 49 | => "sugarfilledMARSIPANcrimsonMARSIPANstripedMARSIPANfrogMARSIPAN860203" 50 | ``` 51 | 52 | ### Dictionaries 53 | TokenPhrase combines words from four different dictionaries: 54 | 55 | * :adjectives 56 | * :colors 57 | * :patterns 58 | * :nouns 59 | 60 | If you want to replace the dictionary, just pass a hash with an array as an argument: 61 | 62 | ```ruby 63 | TokenPhrase.generate :adjectives => %w(glowing) 64 | => "glowing-peach-glossy-barracuda-743220" 65 | 66 | 5.times { p TokenPhrase.generate :nouns => %w(Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto) } 67 | "stupendous-crimson-tartan-Uranus-431203" 68 | "tailored-khaki-fractal-Neptune-957683" 69 | "better-almond-striped-Pluto-299491" 70 | "soft-chiffon-tartan-Saturn-29752" 71 | "tailored-azure-honeycomb-Saturn-668823" 72 | ``` 73 | You can pass multiple dictionaries: 74 | 75 | ```ruby 76 | 5.times { p TokenPhrase.generate :colors => %w(black white), :nouns => %w(cat dog) } 77 | "windy-white-satin-dog-663888" 78 | "exciting-black-spotted-cat-502218" 79 | "sour-white-houndstooth-cat-591001" 80 | "thunderous-white-pinstriped-cat-375006" 81 | "grandpas-white-honeycomb-cat-23992" 82 | ``` 83 | 84 | And you can, of course pass a separator before the dictionaries: 85 | 86 | ```ruby 87 | 5.times { p TokenPhrase.generate '^^^', :patterns => %w(striped), :adjectives =>%w(great awesome) } 88 | "awesome^^^aquamarine^^^striped^^^pen^^^345915" 89 | "great^^^salmon^^^striped^^^pants^^^852927" 90 | "great^^^white^^^striped^^^spider^^^wolf^^^646401" 91 | "awesome^^^blue^^^striped^^^people^^^314195" 92 | "awesome^^^aqua^^^striped^^^wolverine^^^113478" 93 | ``` 94 | 95 | ### Numbers 96 | 97 | To help with uniqueness, a random number is added to the token by default. This may not be your cup of tea, so if you pass :numbers => false with the dictionaries, you can remove the number: 98 | 99 | ```ruby 100 | TokenPhrase.generate(:numbers => false) 101 | => "energetic-yellow-pinstriped-skunk" 102 | ``` 103 | 104 | ## Dictionary Methods 105 | 106 | If you like the existing dictionaries, but want to add your own words, you can use get an array of each dictionary: 107 | 108 | * TokenPhrase.adjectives(more_adjectives = nil) 109 | * TokenPhrase.colors(more_colors = nil) 110 | * TokenPhrase.patterns(more_patterns = nil) 111 | * TokenPhrase.nouns(more_nouns = nil) 112 | 113 | Each of these dictionary methods accept an array as an argument that will merge the existing dictionary with your array 114 | 115 | ```ruby 116 | your_patterns = TokenPhrase.patterns %w(magic-eye) 117 | 2.times { p TokenPhrase.generate :patterns => your_patterns } 118 | "prickly-magenta-matte-space-heater-687093" 119 | "stupendous-aquamarine-magic-eye-skunk-690072" 120 | ``` 121 | 122 | ## TokenPhrase.permutations(dictionaries = {}) 123 | 124 | If you want to see how many unique combinations you can generate, use the permutations method. Just pass it the dictionaries hash the same way you would use TokenPhrase.generate: 125 | 126 | ```ruby 127 | TokenPhrase.permutations 128 | => 4199040000000 129 | 130 | TokenPhrase.permutations :nouns => %w(cat dog) 131 | => 87480000000 132 | 133 | TokenPhrase.permutations :nouns => %w(scooter boat car vroom-vroom), :numbers => false 134 | => 174960 135 | ``` 136 | 137 | ## Rails Uniqueness 138 | 139 | The simplest way to create a unique token for your models is to add a before_create filter to your model: 140 | 141 | ```ruby 142 | #assuming your Thing model has a token property 143 | class Thing < ActiveRecord::Base 144 | before_create :generate_token 145 | 146 | private 147 | def generate_token 148 | begin 149 | self.token = TokenPhrase.generate 150 | end while self.class.exists?(token: token) 151 | end 152 | end 153 | ``` 154 | 155 | ## Contributing 156 | 157 | 1. Fork it 158 | 2. Create your feature branch (`git checkout -b my-new-feature`) 159 | 3. Commit your changes (`git commit -am 'Add some feature'`) 160 | 4. Push to the branch (`git push origin my-new-feature`) 161 | 5. Create new Pull Request 162 | 163 | ## Thanks 164 | 165 | Thanks to [benolee](https://github.com/benolee) for [refactoring the generator model](https://github.com/genericsteele/token_phrase/commit/7e5a0877882ba7d08f0c5a40d7873ebb0d205a45)! 166 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rake/testtask' 3 | 4 | Rake::TestTask.new do |t| 5 | t.libs << 'test' 6 | t.test_files = FileList['test/*.rb'] 7 | end 8 | 9 | desc "Run tests" 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /lib/token_phrase.rb: -------------------------------------------------------------------------------- 1 | require "token_phrase/dictionary" 2 | require "token_phrase/generator" 3 | require "token_phrase/version" 4 | 5 | module TokenPhrase 6 | def self.generate *args 7 | Generator.new(*args).generate 8 | end 9 | 10 | def self.permutations *args 11 | Generator.new(*args).permutations 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/token_phrase/dictionary.rb: -------------------------------------------------------------------------------- 1 | module TokenPhrase 2 | RANDOM_NUMBER_UPPER_LIMIT = 1000000 3 | 4 | def self.dictionary 5 | { 6 | :adjectives => %w(wireless furry fuzzy sleek messy incredible generous splendid superior spectacular amazing ultimate ferocious exciting lovely old-fashioned home-made grass-fed free-range grandmas grandpas governing prickly strong stellar awesome wonderful bodacious excellent stupendous groovy dancing energetic sweet sour sugarfilled glazed vegan letterman thunderous established magnetic better windy wind-up american soft genetically-modified tailored liberal conservative bluetooth), 7 | :colors => %w(red yellow blue green violet taupe mauve lime golden silver grey black white tangello sunshine brown tan infrared ultraviolet pink beige almond aquamarine burnt-orange cerulean cornflower-blue denim forest-green midnight-blue peach plum sea-green ruby emerald jade rose topaz onyx pearl coral crimson cyan chocolate aqua azure lavendar chiffon khaki ivory magenta navy-blue olive salmon turquoise), 8 | :patterns => %w(striped checked spotted polka-dotted plaid wavy houndstooth argyle glossy matte pinstriped tartan paisley satin honeycomb fractal waved cracked ), 9 | :nouns => %w(floutist carpenter jacket president address machine computer mug lamp phone wall bicycle river lake fountain building book hat pants shirt cape soup gloves pen suit photograph sand profit energy fork compact-disk floppy-disk chandelier door window laboratory people tapir wolverine wolf spider wolf-spider spider-wolf banana-slug giraffe deer-mouse capybara dingo dragon cardinal owl octopus elk moose weasel elephant rhino iguana bullfrog greyhound stickbug ladybug ant rat coyote chimpanzee housecat barracuda raven crane fox panda racoon nessie whale dolphin shark viper frog toad flounder skunk wookie dishwasher bat space-heater bobble-head lego-set pinboard flag tv video-game envelope headphones mousepad jukebox bacon eggs cereal milk sausage ham turkey nerf-gun bowl plate lazy-susan safe fireworks table chair muscles trek journey quest mission laser-gun gladiator pumpkin), 10 | :numbers => [SecureRandom.random_number(RANDOM_NUMBER_UPPER_LIMIT)] 11 | } 12 | end 13 | 14 | def self.adjectives more = [] 15 | dictionary[:adjectives] | more 16 | end 17 | 18 | def self.colors more = [] 19 | dictionary[:colors] | more 20 | end 21 | 22 | def self.patterns more = [] 23 | dictionary[:patterns] | more 24 | end 25 | 26 | def self.nouns more = [] 27 | dictionary[:nouns] | more 28 | end 29 | 30 | def self.numbers more = [] 31 | dictionary[:numbers] | more 32 | end 33 | end 34 | 35 | -------------------------------------------------------------------------------- /lib/token_phrase/generator.rb: -------------------------------------------------------------------------------- 1 | require 'backports/1.9.1/array/sample' 2 | module TokenPhrase 3 | class Generator 4 | attr_accessor :separator, :dictionary, :order 5 | 6 | def initialize separator = "-", options = {} 7 | separator, options = "-", separator if separator.is_a? Hash 8 | options[:numbers] = [] if options[:numbers] == false 9 | 10 | @separator = separator 11 | @dictionary = TokenPhrase.dictionary.merge options 12 | @order = dictionary.keys 13 | end 14 | 15 | def generate 16 | lists.map(&:sample).join(separator).chomp(separator).gsub(/-/, separator) 17 | end 18 | 19 | def permutations 20 | lists.inject 1 do |p, list| 21 | if list.empty? 22 | p 23 | else 24 | p * list.uniq.count 25 | end 26 | end 27 | end 28 | 29 | def lists 30 | dictionary.values_at(*order).compact 31 | end 32 | end 33 | end 34 | 35 | -------------------------------------------------------------------------------- /lib/token_phrase/version.rb: -------------------------------------------------------------------------------- 1 | module TokenPhrase 2 | VERSION = "1.0.6" 3 | end 4 | -------------------------------------------------------------------------------- /test/generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'token_phrase' 3 | 4 | class GeneratorTest < MiniTest::Unit::TestCase 5 | def test_that_numbers_can_be_excluded 6 | phrase = TokenPhrase.generate :numbers => false 7 | refute_match /\d+$/, phrase 8 | end 9 | 10 | def test_that_permutations_calculate_all_combinations 11 | permutations = TokenPhrase.permutations( 12 | :adjectives => %w(test), 13 | :colors => %w(test), 14 | :patterns => %w(test), 15 | :nouns => %w(test), 16 | :numbers => false 17 | ) 18 | assert_equal 1, permutations 19 | end 20 | 21 | def test_that_separator_can_be_overridden 22 | phrase = TokenPhrase.generate "+" 23 | assert_match /\+/, phrase 24 | refute_match /-/, phrase 25 | end 26 | 27 | def test_that_adjectives_can_be_overridden 28 | phrase = TokenPhrase.generate :adjectives => %w(test) 29 | assert_match /test/, phrase 30 | end 31 | 32 | def test_that_colors_can_be_overridden 33 | phrase = TokenPhrase.generate :colors => %w(test) 34 | assert_match /test/, phrase 35 | end 36 | 37 | def test_that_patterns_can_be_overridden 38 | phrase = TokenPhrase.generate :patterns => %w(test) 39 | assert_match /test/, phrase 40 | end 41 | 42 | def test_that_nouns_can_be_overridden 43 | phrase = TokenPhrase.generate :nouns => %w(test) 44 | assert_match /test/, phrase 45 | end 46 | 47 | def test_default_numbers_is_an_integer 48 | numbers = TokenPhrase.numbers.sample 49 | assert numbers.is_a? Integer 50 | end 51 | 52 | def test_default_numbers_within_upper_limit 53 | numbers = TokenPhrase.numbers.sample 54 | assert numbers.between?(0, TokenPhrase::RANDOM_NUMBER_UPPER_LIMIT) 55 | end 56 | 57 | def test_that_dictionaries_can_be_added_to 58 | adjectives = TokenPhrase.adjectives %w(test) 59 | colors = TokenPhrase.colors %w(test) 60 | patterns = TokenPhrase.patterns %w(test) 61 | nouns = TokenPhrase.nouns %w(test) 62 | assert_equal (TokenPhrase.adjectives.count + 1), adjectives.count 63 | assert_equal (TokenPhrase.colors.count + 1), colors.count 64 | assert_equal (TokenPhrase.patterns.count + 1), patterns.count 65 | assert_equal (TokenPhrase.nouns.count + 1), nouns.count 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | -------------------------------------------------------------------------------- /token_phrase.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'token_phrase/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.add_dependency 'backports' 8 | gem.add_development_dependency 'minitest' 9 | 10 | gem.name = "token_phrase" 11 | gem.version = TokenPhrase::VERSION 12 | gem.authors = ["Eric Steele"] 13 | gem.email = ["eric@notvelcro.com"] 14 | gem.description = %q{A token-phrase generator} 15 | gem.summary = %q{Token Phrase is a simple generator that creates friendlier unique tokens as phrases} 16 | gem.homepage = "https://github.com/genericsteele/token_phrase" 17 | 18 | gem.files = `git ls-files`.split($/) 19 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 20 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 21 | gem.require_paths = ["lib"] 22 | end 23 | --------------------------------------------------------------------------------