begin require 'active_support/all' rescue LoadError require 'active_support' end require 'uuidtools' require 'huge_enumerable' module PseudoEntity::Randoms require 'pseudo_entity/randoms/constants' require 'pseudo_entity/randoms/location' # These are the randoms values available and are neither composites nor subsets of anything else in this list. RandomValues = [:apartment_number, :bank_routing_number, :bank_account_number, :bank_name, :birth_day, :city, :company_name, :country_code, :credit_card_issuer, :credit_card_number, :credit_union_name, :domain, :email_address, :federal_tax_id, :first_name, :ip_address, :last_name, :latitude, :login, :longitude, :password, :phone_number, :property_number, :sex, :time_shift, :short_url, :slogan, :state, :street_name, :suite_number, :website, :uuid, :time_zone, :zip_code, :facebook_id, :review, :salt, :iv, :google_analytics_account_id, :region, :subject, :noun, :adjective ] ArityValues = [/^numeric_\d+$/, /^alpha_\d+$/, /^alpha_numeric_\d+$/, /^token_\d+$/, /^rand_\d+$/] ArityRandoms = [/^random_numeric_\d+$/, /^random_alpha_\d+$/, /^random_alpha_numeric_\d+$/, /^random_token_\d+$/] include Constants # Create the lazy loading accessors RandomValues.each do |value_name| class_eval %{ def #{value_name} @#{value_name} ||= random_#{value_name} end } end # Im still not happy about this but at least it is out of PseudoEntity. def shift_time(options={:distance => :days, :direction => :either, :base => nil, :min => nil, :max => nil}) random_time_shift options end def method_missing(name, *args, &block) if arity_random?(name) # Define random_X helpers on the fly size = /.*_(\d+)$/.match(name.to_s)[1].to_i base_name = (/(.*)_\d+$/.match(name.to_s)[1]).to_sym self.class.send(:define_method, name) do self.send(base_name, size) end self.class.send(:private, name) send(name) elsif arity_value?(name) iv_name = "@#{name}".to_sym command = "random_#{name}".to_sym relay = arity_random?(command) if !relay size = /.*_(\d+)$/.match(name.to_s)[1].to_i command = (/(.*)_\d+$/.match(name.to_s)[1]).to_sym object = Kernel end self.class.send(:define_method, name) do iv = instance_variable_get(iv_name) if iv.nil? iv = relay ? send(command) : object.send(command, size) instance_variable_set(iv_name, iv) end iv end send(name) else super end end def respond_to?(method, include_all=false) include_all && arity_random?(method) || arity_value?(method) || super end if RUBY_VERSION < "1.9" def respond_to_missing?(method, include_all=false) include_all && arity_random?(method) || arity_value?(method) || super end if RUBY_VERSION >= "1.9" private def parse_options(options={}) # Make an instance variable for each randoms value and set it to anything sent in for it via the options. RandomValues.each { |value_name| instance_variable_set("@#{value_name}".to_sym,options[value_name]) } end def arity_random?(name) name = name.to_s ArityRandoms.any? { |x| x =~ name } end def arity_value?(name) name = name.to_s ArityValues.any? { |x| x =~ name } end def random_adjective PseudoEntity::Randoms.adjectives.pop end def random_alpha(size) chars = ('a'..'z').to_a + ('A'..'Z').to_a (1..size).map { chars[rand(chars.size)] }.join end def random_alpha_numeric(size) chars = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a (1..size).map { chars[rand(chars.size)] }.join end def random_apartment_number rand(998) + 1 end def random_bank_account_number random_numeric(13) end def random_bank_name PseudoEntity::Randoms.bank_names.pop end def random_bank_routing_number random_numeric(9) end def random_birth_day # Everyone will be between 21 and 101 years old. Date.today - 21.years - rand(80 * 365).days end def random_city PseudoEntity::Randoms.cities.pop end def random_class_a_ip_address #10.0.0.0 - 10.255.255.255 16777216 "10.#{rand(255)}.#{rand(255)}.#{rand(255)}" end def random_class_b_ip_address #172.16.0.0 - 172.31.255.255 1048576 "172.#{rand(15) + 16}.#{rand(255)}.#{rand(255)}" end def random_class_c_ip_address #192.168.0.0 - 192.168.255.255 65536 "192.168.#{rand(255)}.#{rand(255)}" end def random_company_name PseudoEntity::Randoms.company_names.pop end def random_country_code location.country_code end def random_credit_card PseudoEntity::Randoms.credit_cards.pop end def random_credit_card_issuer credit_card.first end def random_credit_card_number credit_card.last end def random_credit_union_name PseudoEntity::Randoms.credit_union_names.pop end def random_domain PseudoEntity::Randoms.domains.pop end def random_email_address "#{first_name}.#{last_name}@#{domain}" end def random_facebook_id "%.20i" % rand(2**64) end def random_federal_tax_id random_numeric(9) end def random_first_name PseudoEntity::Randoms.first_names.pop end def random_google_analytics_account_id "UA-#{random_numeric(7)}-#{random_numeric(2)}" end def random_ip_address class_a_size = 16777216 class_b_size = 1048576 class_c_size = 65536 subnet = rand(class_a_size + class_b_size + class_c_size) if subnet < class_a_size random_class_a_ip_address elsif subnet < class_a_size + class_b_size random_class_b_ip_address else random_class_c_ip_address end end def random_iv OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_iv end def random_last_name PseudoEntity::Randoms.last_names.pop end def random_latitude location.latitude end def random_login "#{last_name.downcase}_#{first_name.downcase}" end def random_longitude location.longitude end def random_noun PseudoEntity::Randoms.nouns.pop end def random_password # Bet they will never guess this one! :P "#{first_name.downcase}_#{last_name.downcase}" end def random_phone_number "#{location.area_code}-#{random_numeric(3)}-#{random_numeric(4)}" end def random_property_number rand(9989) + 10 end def random_region PseudoEntity::Randoms.regions.pop end def random_review PseudoEntity::Randoms.reviews.pop end def random_salt Time.now.to_i.to_s end def random_sex PseudoEntity::Randoms.sex_of(first_name) || ['Female', 'Male'].sample end def random_short_url "http://ti.ny/#{domain.hash}" end def random_slogan PseudoEntity::Randoms.slogans.pop end def random_state location.state end def random_street_modifier_needed # One out of ten addresses gets something. if rand(10) == 0 # 70% of the time it is a suite. if rand(10) < 7 :suite else :apartment end else # The rest of the time we do not need to generate one. false end end def random_street_name PseudoEntity::Randoms.street_names.pop end def random_numeric(size) "%.#{size}i" % rand(10**size) end def random_subject PseudoEntity::Randoms.subjects.pop end def random_suite_number rand(498) + 1 end def random_time_between(min, max) PseudoEntity::Randoms.time_between min, max end def random_time_shift(options={:distance => :days, :direction => :either, :base => nil, :min => nil, :max => nil}) distance = options[:distance] || :days direction = options[:direction] || :either base = options[:base] min = options[:min] max = options[:max] shift = PseudoEntity::Randoms.time_shift distance, direction if base conversion = case base when DateTime :to_datetime when Date :to_date when Time :to_time else nil end new_time = base.to_datetime + shift if max && new_time > max.to_datetime new_time = max elsif min && new_time < min.to_datetime new_time = min end conversion ? new_time.send(conversion) : new_time else shift end end def random_time_zone location.time_zone end def random_token(size) PseudoEntity::Randoms.token(size) end def random_token_8 PseudoEntity::Randoms.token(8) end def random_token_16 PseudoEntity::Randoms.token(16) end def random_token_32 PseudoEntity::Randoms.token(32) end def random_token_64 PseudoEntity::Randoms.token(64) end def random_uuid PseudoEntity::Randoms.uuid end def random_website "http://#{domain}" end def random_zip_code location.zip_code end ##### Helpers for above ##### def credit_card @credit_card ||= random_credit_card end def determine_street_modifier_needed # If a property number has already been set then we assume that any required apartment or suite number has also been set. if @property_number if @apartment_number :apartment elsif @suite_number :suite else false end else random_street_modifier_needed end end def location @location ||= PseudoEntity::Randoms.locations.pop end def street_modifier_needed @street_modifier_needed ||= determine_street_modifier_needed end alias street_modifier_needed? street_modifier_needed def street_position "#{property_number} #{street_name}" end # This method allows us to build data sets that will refresh automatically when they are blanked. # The initial set of data comes from the supplied block. That block will only be run once and the results will be cached. # Each time the data set is blanked, it will be reloaded from cache and shuffled (if the data set supports the operation). # By removing the values out of the data set, instead of using sample, you are guaranteed to not have repeats until the data set is refreshed and shuffled again. def self.data_for(name, &block) # Grab the data set data = data_get(name) # If it is out of data or has never been initialized in the first place if data.blank? # Grab the internal cache of all the generated data original_data = original_data_get(name) # If it also has never been initialized if original_data.nil? # Call the block and get the full set of data original_data = yield # Store the data so we never have to call that block again original_data_set(name, original_data) end # Reset the data set that will be given to callers. They can do whatever with it. If the data set is cleared it will just be refreshed from cache. data = data_set(name, original_data.respond_to?(:shuffle) ? original_data.shuffle : original_data.dup) end # Give them what they asked for data end # Now generate the infinite arrays/enumerables. def self.adjectives data_for_adjectives_is { HugeCollection.new(ADJECTIVES) } end def self.subjects data_for_subjects_is { RandomSubjects.new(ADJECTIVES, NOUNS) } end def self.bank_names data_for_bank_names_is { RandomBankNames.new(BANK_PREFIXES) } end def self.cities data_for_cities_is { HugeCollection.new(CITIES) } end def self.company_names data_for_company_names_is { RandomCompanyNames.new(ADJECTIVES, NOUNS, COMPANY_TYPES) } end def self.company_types data_for_company_types_is { HugeCollection.new(COMPANY_TYPES) } end def self.credit_cards data_for_credit_cards_is { HugeCollection.new(CREDIT_CARDS) } end def self.credit_union_names data_for_bank_names_is { RandomCreditUnionNames.new(CREDIT_UNION_PREFIXES) } end def self.domains data_for_domains_is { RandomDomains.new(NOUNS, DOMAINS) } end def self.female_first_names data_for_female_first_names_is { HugeCollection.new(FEMALE_FIRST_NAMES) } end def self.first_names data_for_first_names_is { HugeCollection.new(FEMALE_FIRST_NAMES + MALE_FIRST_NAMES) } end def self.last_names data_for_last_names_is { HugeCollection.new(SURNAMES) } end def self.locations data_for_locations_is { RandomLocations.new(LOCATIONS_HASH) } end def self.female_names data_for_female_names_is { HugeProduct.new(FEMALE_FIRST_NAMES, SURNAMES) } end def self.female_full_names data_for_female_full_names_is { RandomNames.new(FEMALE_FIRST_NAMES, SURNAMES) } end def self.full_names data_for_full_names_is { RandomNames.new(FEMALE_FIRST_NAMES + MALE_FIRST_NAMES, SURNAMES) } end def self.male_names data_for_male_names_is { HugeProduct.new(MALE_FIRST_NAMES, SURNAMES) } end def self.male_first_names data_for_male_first_names_is { HugeCollection.new(MALE_FIRST_NAMES) } end def self.male_full_names data_for_male_full_names_is { RandomNames.new(MALE_FIRST_NAMES, SURNAMES) } end def self.names data_for_names_is { HugeProduct.new(FEMALE_FIRST_NAMES + MALE_FIRST_NAMES, SURNAMES) } end def self.nouns data_for_nouns_is { HugeCollection.new(NOUNS) } end def self.time_between(min, max) conversion = case min when DateTime :to_datetime when Date :to_date when Time :to_time else nil end min = min.to_datetime max = max.to_datetime new_time = min + (max - min) * rand new_time = new_time.send(conversion) if conversion new_time end def self.regions data_for_regions_is { HugeCollection.new(REGIONS) } end def self.reviews data_for_reviews_is { RandomReviews.new(review_patterns, ADJECTIVES, NOUNS) } end def self.review_patterns data_for_review_patterns_is { HugeCollection.new(REVIEW_PATTERNS) } end def self.sex_of(first_name) if FEMALE_FIRST_NAMES.include?(first_name) 'Female' elsif MALE_FIRST_NAMES.include?(first_name) 'Male' else nil end end def self.slogans data_for_slogans_is { RandomSlogans.new(SLOGAN_PATTERNS, ADJECTIVES, NOUNS) } end def self.slogan_patterns data_for_slogan_patterns_is { HugeCollection.new(SLOGAN_PATTERNS) } end def self.street_names data_for_street_names_is { RandomStreetNames.new(STREET_POSITIONS, STREET_NAMES) } end def self.time_shift(distance = :days, direction = nil) shift = case distance when :centuries 1000.years when :decades 100.years when :years 10.years when :months 12.months when :days 30.days when :hours 24.hours when :minutes 60.minutes when :seconds 60.seconds else distance end.to_f * rand direction = direction.to_s if direction shift *= -1 if direction.starts_with?('backward') || (!direction || direction == 'either') && rand(2) == 1 shift end def self.token(size) (1..(size + 31) / 32).map { UUIDTools::UUID.random_create.hexdigest }.join('')[0..size-1] end def self.uuid UUIDTools::UUID.random_create.to_s end def self.zip_code_state_hash data_for_zip_code_state_hash_is do with_fresh_locations do locations.inject({}) do |hsh, location| hsh[location.zip_code] = location.state hsh end end end end def self.zip_code_states data_for_zip_code_states_is { zip_code_state_hash.to_a } end # Convert the string, name, into a symbol representing the instance variable. :@name def self.data_iv_sym(name) "@#{name.to_s}".to_sym end # Using the string, name, return the value from the instance variable, @name def self.data_get(name) instance_variable_get(data_iv_sym(name)) end def self.data_set(name, array) instance_variable_set(data_iv_sym(name), array) end # Convert the string, name, into a symbol representing the cached instance variable. :@original_name def self.original_data_iv_sym(name) "@original_#{name.to_s}".to_sym end # Using the string, name, return the value from the instance variable, @original_name def self.original_data_get(name) instance_variable_get(original_data_iv_sym(name)) end def self.original_data_set(name, array) instance_variable_set(original_data_iv_sym(name), array) end # Clear out one of the data sets being used, returning back what was in it. def self.clear_data(name) remaining_data = data_get(name) data_set(name, nil) remaining_data end # Save what is currently in the cached array, reload the cache, run a block, and then restore the cached array with what was originally there. def self.with_fresh(name, *other_names, &block) old_values = clear_data(name) next_name = other_names.shift results = next_name ? with_fresh(next_name, *other_names, &block) : yield data_set(name, old_values) results end def self.preload %w{ adjectives subjects bank_names cities company_names company_types credit_cards credit_union_names domains female_first_names first_names last_names locations female_names female_full_names full_names male_names male_first_names male_full_names names nouns regions slogans slogan_patterns street_names zip_code_state_hash zip_code_states }.each do |name| puts "Preloading #{name}" puts "#{self.send(name).size} #{name} loaded." end puts "Done" end def self.method_missing(name, *args, &block) name_s = name.to_s if name_s[0..10] == "with_fresh_" with_fresh(name_s[11..-1], &block) elsif name_s[0..8] == "data_for_" && name_s[-3..-1] == "_is" data_for(name_s[9..-4], &block) else super super end end class RandomBankNames < HugeCombination # BANK_PREFIXES.combination(2).map { |x| (x << "Bank").join(' ') }} def initialize(bank_prefixes) super(bank_prefixes, 2) end def fetch(x) (super(x) << "Bank").join(' ') end end class RandomCompanyNames < HugeProduct #adjectives.product(nouns).product(company_types).map { |x| x.flatten.join(' ') } def initialize(adjectives, nouns, company_types) adjective_nouns = HugeProduct.new(adjectives.map(&:titleize), nouns.map(&:titleize)) super(adjective_nouns, company_types.map(&:titleize)) end def fetch(x) super(x).flatten.join(' ') end end class RandomCreditUnionNames < HugeCombination # CREDIT_UNION_PREFIXES.combination(2).map { |x| (x << "Credit Union").join(' ') } def initialize(credit_union_prefixes) super(credit_union_prefixes, 2) end def fetch(x) (super(x) << "Credit Union").join(' ') end end class RandomDomains < HugeProduct #nouns.permutation(2).map(&:to_s).product(DOMAINS).map { |x| x.join('.') } def initialize(nouns, top_level_domains) @nouns = HugePermutation.new(nouns, 2) super(@nouns, top_level_domains) end def fetch(x) product = super(x) nouns = product.first "#{nouns.join}.#{product.last}" end end class RandomNames < HugeProduct def initialize(first_names, last_names) super(first_names, last_names) end def fetch(x) super(x).join(' ') end end class RandomLocations < HugeCollection def initialize(locations) super(locations.dup) end def fetch(x) location = super(x) if location.is_a?(Hash) location = Location.new(location) enum[x] = location end location end end class RandomReviews < HugeProduct #review_patterns.product(adjectives.product(nouns).permutation(2)) def initialize(review_patterns, adjectives, nouns) subjects = HugeProduct.new(adjectives, nouns) combined_subjects = HugePermutation.new(subjects, 2) super(review_patterns, combined_subjects) end def fetch(x) review = super(x) review.first % review.last.flatten end end class RandomSlogans < HugeProduct #slogan_patterns.product(adjectives).product(nouns).map { |x| x[0][0] % [x[0][1], x[1]] } def initialize(slogan_patterns, adjectives, nouns) subjects = HugeProduct.new(adjectives, nouns) super(slogan_patterns, subjects) end def fetch(x) slogan = super(x) slogan.first % slogan.last end end class RandomStreetNames < HugeProduct def initialize(street_positions, street_names) super(street_positions, street_names) end def fetch(x) street = super(x) # Slightly increase the likelihood of a repetition by preventing silly names like "SE Main Street North" if ["North", "South", "East", "West"].any? { |x| street.last.include?(x) } street.last else street.join(' ') end end end class RandomSubjects < HugeProduct #adjectives.product(nouns).map { |x| x.join(' ') } def initialize(adjectives, nouns) super(adjectives, nouns) end def fetch(x) super(x).join(' ') end end end