├── .github └── workflows │ └── test.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── ci └── spec ├── lib ├── to_factory.rb └── to_factory │ ├── collation.rb │ ├── config.rb │ ├── file_sync.rb │ ├── file_writer.rb │ ├── finders │ ├── factory.rb │ └── model.rb │ ├── generation │ ├── attribute.rb │ └── factory.rb │ ├── klass_inference.rb │ ├── options_parser.rb │ ├── parsing │ ├── file.rb │ └── ruby_parsing_helpers.rb │ ├── representation.rb │ └── version.rb ├── spec ├── db │ └── migrate │ │ ├── 1_create_users.rb │ │ ├── 2_create_projects.rb │ │ ├── 3_create_not_namespaced.rb │ │ ├── 4_add_birthday_to_users.rb │ │ └── 5_add_serialized_attributes_to_users.rb ├── example_factories │ ├── admin.rb │ ├── admin_with_header.rb │ ├── inherited_project_with_header.rb │ ├── project_with_header.rb │ ├── user.rb │ ├── user_admin.rb │ ├── user_admin_root.rb │ ├── user_admin_super_admin.rb │ ├── user_admin_with_header.rb │ └── user_with_header.rb ├── integration │ ├── config_spec.rb │ ├── empty_factory_file_spec.rb │ ├── file_sync_spec.rb │ ├── file_writer_spec.rb │ ├── lint_spec.rb │ ├── multiple_to_factory_calls_spec.rb │ ├── non_active_record_classes_spec.rb │ └── to_factory_method_spec.rb ├── spec_helper.rb ├── support │ ├── broken_models │ │ ├── invalid_ruby_file.rb │ │ └── project.rb │ ├── data_creation.rb │ ├── match_sexp.rb │ ├── models │ │ ├── not_active_record.rb │ │ ├── project.rb │ │ └── user.rb │ ├── non_active_record │ │ ├── inherited_project.rb │ │ ├── project.rb │ │ ├── some_other_service_inheriting_from_something_else.rb │ │ ├── some_service.rb │ │ └── something_else.rb │ └── ruby_parser_exception_causing_string.rb └── unit │ ├── collation_spec.rb │ ├── file_writer_spec.rb │ ├── finders │ ├── factory_spec.rb │ └── model_spec.rb │ ├── generation │ ├── attribute_spec.rb │ └── factory_spec.rb │ └── parsing │ ├── file_spec.rb │ └── klass_inference_spec.rb ├── tmp └── .keep └── to_factory.gemspec /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: RSpec 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | rspec: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: ruby/setup-ruby@v1 11 | with: 12 | bundler-cache: false 13 | ruby-version: 2.7.5 14 | - uses: actions/cache@v2 15 | with: 16 | path: .github/vendor/bundle 17 | key: bundle-use-ruby-for-danger-${{ matrix.os }}-${{ matrix.ruby }}-${{ hashFiles('**/Gemfile.lock') }} 18 | restore-keys: | 19 | bundle-use-ruby-for-danger-${{ matrix.os }}-${{ matrix.ruby }}- 20 | - name: bundle install 21 | run: | 22 | cd .github 23 | bundle install --path=vendor/bundle --jobs 4 --retry 3 24 | - name: setup db 25 | run: | 26 | bundle exec rake spec:migrate_db 27 | - name: RSpec tests 28 | run: bundle exec rspec -f j -o tmp/rspec_results.json -f p 29 | 30 | - name: RSpec Report 31 | uses: SonicGarden/rspec-report-action@v2 32 | with: 33 | token: ${{ secrets.GITHUB_TOKEN }} 34 | json-path: tmp/rspec_results.json 35 | if: always() 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | pkg/* 4 | *.swp 5 | tmp/database.log 6 | Gemfile.lock 7 | coverage/ 8 | spec/db/test.sqlite3 9 | tmp/* 10 | !tmp/.keep 11 | *.sqlite3 12 | /spec/dummy/spec/factories 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## master 4 | 5 | ## 3.0.0 (2022-12-31) 6 | 7 | ### Support FactoryBot 8 | 9 | * Add support for FactoryBot syntax 10 | * drop FactoryGirl support 11 | * drop any references to old or older FactoryGirl syntax 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :test do 4 | gem 'simplecov', require: false 5 | gem 'rspec-github', require: false 6 | gem 'byebug' 7 | gem 'pry' 8 | end 9 | 10 | gemspec 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ToFactory :wrench: 2 | ========= 3 | 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/34f4377c5b911bcb4433/maintainability)](https://codeclimate.com/github/markburns/to_factory/maintainability) 5 | [![Test Coverage](https://api.codeclimate.com/v1/badges/34f4377c5b911bcb4433/test_coverage)](https://codeclimate.com/github/markburns/to_factory/test_coverage) 6 | [![Gem Version](http://img.shields.io/gem/v/to_factory.svg)](https://rubygems.org/gems/to_factory) 7 | [![License](http://img.shields.io/:license-mit-blue.svg)](http://markburns.mit-license.org) 8 | 9 | Easily add factories with valid data for an existing project. 10 | 11 | If you find yourself retro-fitting tests this gem will save you some of the legwork. 12 | 13 | * auto-generate all factories 14 | * adhoc generate from existing records 15 | * unintrusively update factory files in place 16 | * display factory definition for a record 17 | * parse and write `FactoryBot` syntax 18 | 19 | Tested against Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x, 2.2.x 20 | 21 | ## Warning :warning: 22 | `ToFactory` writes into the `spec/factories` folder. Whilst it 23 | is tested and avoids overwriting existing factories, 24 | it is recommended that you execute after committing or when in a known 25 | safe state. 26 | 27 | 28 | 29 | 30 | ## Installation :file_folder: 31 | 32 | ```ruby 33 | 34 | #Gemfile 35 | #add to whichever environments you want to generate data from 36 | group :test, :development do 37 | gem 'to_factory' 38 | end 39 | ``` 40 | 41 | 42 | ```bash 43 | git add spec/factories 44 | git commit -m "I know what I am doing" 45 | rails c 46 | >ToFactory() 47 | ``` 48 | ## Example :computer: 49 | 50 | ```ruby 51 | #Generate all factories 52 | ToFactory() 53 | #outputs the first record of each ActiveRecord::Base subclass in the models folder 54 | #to spec/factories 55 | 56 | #Choose input/output directories 57 | ToFactory.models = "models/this/subfolder/only" #default "./app/models" 58 | ToFactory.factories = "spec/support/factories" #default "./spec/factories" 59 | ToFactory() 60 | 61 | #Exclude classes 62 | ToFactory(exclude: [User, Project]) 63 | 64 | #Use Adhoc instances from the console 65 | ToFactory User.last 66 | 67 | #writes to spec/factories/user.rb 68 | FactoryBot.define 69 | factory(:user) do |u| 70 | email "test@example.com" 71 | name "Mike" 72 | end 73 | end 74 | 75 | #List defined factory names 76 | ToFactory.definitions 77 | #=> [:user, :admin, :project] 78 | 79 | #Display definition from record 80 | ToFactory.definition_for @user 81 | 82 | #Display existing definition from name 83 | ToFactory.definition_for :admin 84 | 85 | #doesn't overwrite existing factories 86 | ToFactory User.last 87 | #Exception => 88 | #ToFactory::AlreadyExists: an item for each of the following keys :user already exists 89 | 90 | #Choose specific name 91 | ToFactory :admin => User.last 92 | #appends to spec/factories/user.rb 93 | 94 | ``` 95 | 96 | ### Other useful projects 97 | 98 | If you are adding specs to an existing project you may want to look at: 99 | 100 | * [rspec-kickstarter](https://github.com/seratch/rspec-kickstarter) (auto generate specs) 101 | * [rspec-kickstarter-vintage](https://github.com/ifad/rspec-kickstarter-vintage) (for Ruby 1.8/Rspec 1.x) 102 | * [hash_syntax](https://github.com/michaeledgar/hash_syntax) (convert 1.8 syntax to 1.9 and vice-versa) 103 | * [transpec](https://github.com/yujinakayama/transpec) (convert old rspec syntax to new expect syntax) 104 | 105 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'byebug' 3 | 4 | namespace :spec do 5 | def setup_db 6 | require "logger" 7 | require "active_record" 8 | ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "spec/db/test.sqlite3") 9 | ActiveRecord::Base.logger = Logger.new(File.open("tmp/database.log", "a")) 10 | ActiveRecord::Migrator.migrations_paths = File.expand_path("./spec/db/migrate") 11 | end 12 | 13 | desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x" 14 | task :migrate_db do 15 | setup_db 16 | ActiveRecord::Tasks::DatabaseTasks.migrate 17 | end 18 | 19 | desc "Migrate down" 20 | task :migrate_down do 21 | setup_db 22 | ActiveRecord::Migrator.new.migrate(:down) 23 | end 24 | end 25 | 26 | begin 27 | require "rspec/core/rake_task" 28 | RSpec::Core::RakeTask.new do |t| 29 | t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"] 30 | t.pattern = "spec/**/*_spec.rb" 31 | end 32 | task default: :spec 33 | rescue LoadError 34 | 35 | end 36 | 37 | begin 38 | require "rubocop/rake_task" 39 | 40 | RuboCop::RakeTask.new 41 | rescue LoadError 42 | end 43 | -------------------------------------------------------------------------------- /bin/ci: -------------------------------------------------------------------------------- 1 | mkdir tmp/ 2 | CODECLIMATE_REPO_TOKEN=7ed88deb7375c6d5a3c87f7744987e3e5631bee4ff85c0599db2949d23158568 ./bin/spec 3 | -------------------------------------------------------------------------------- /bin/spec: -------------------------------------------------------------------------------- 1 | bundle exec rake spec:migrate_db 2 | bundle exec rake 3 | -------------------------------------------------------------------------------- /lib/to_factory.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | require 'to_factory/version' 4 | require 'to_factory/config' 5 | require 'to_factory/generation/factory' 6 | require 'to_factory/generation/attribute' 7 | require 'to_factory/collation' 8 | require 'to_factory/file_writer' 9 | require 'to_factory/finders/model' 10 | require 'to_factory/finders/factory' 11 | require 'to_factory/file_sync' 12 | require 'to_factory/parsing/file' 13 | require 'to_factory/representation' 14 | require 'to_factory/klass_inference' 15 | require 'to_factory/options_parser' 16 | # require "factory_bot" 17 | 18 | module ToFactory 19 | class MissingActiveRecordInstanceException < Exception; end 20 | class NotFoundError < Exception; end 21 | 22 | class << self 23 | def definition_for(item) 24 | if item.is_a? ActiveRecord::Base 25 | Representation.from(item).definition 26 | elsif found = representations.find { |r| r.name.to_s == item.to_s } 27 | found.definition 28 | else 29 | raise NotFoundError, "No definition found for #{item}" 30 | end 31 | end 32 | 33 | def definitions 34 | representations.map(&:name) 35 | end 36 | 37 | def representations 38 | Finders::Factory.new.call 39 | end 40 | end 41 | end 42 | 43 | public 44 | 45 | def ToFactory(args = nil) 46 | exclusions = if args.is_a?(Hash) 47 | exclusions = Array(args.delete(:exclude) || []) 48 | args = nil if args.keys.length == 0 49 | exclusions 50 | else 51 | [] 52 | end 53 | 54 | meth = ToFactory::FileSync.method(:new) 55 | sync = args ? meth.call(args) : meth.call 56 | 57 | sync.perform(exclusions) 58 | end 59 | -------------------------------------------------------------------------------- /lib/to_factory/collation.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | AlreadyExists = Class.new ArgumentError 3 | 4 | class Collation 5 | def self.organize(a, b) 6 | new(a, b).organize 7 | end 8 | 9 | def self.representations_from(a, b) 10 | new(a, b).representations 11 | end 12 | 13 | def initialize(a, b) 14 | @a = a 15 | @b = b 16 | end 17 | 18 | def organize 19 | representations.group_by { |i| i.klass.name.underscore }.each_with_object({}) do |(klass_name, r), o| 20 | o[klass_name] = r.sort_by { |r| [r.hierarchy_order, r.name] } 21 | end 22 | end 23 | 24 | def representations 25 | detect_collisions!(@a, @b) 26 | 27 | inference = KlassInference.new(merged) 28 | 29 | merged.each do |r| 30 | klass, order = inference.infer(r.name) 31 | r.klass = klass 32 | r.hierarchy_order = order 33 | end 34 | 35 | merged 36 | end 37 | 38 | def detect_collisions!(a, b) 39 | collisions = [] 40 | a.each do |x| 41 | b.each do |y| 42 | collisions << x.name if x.name == y.name 43 | end 44 | end 45 | 46 | raise_already_exists!(collisions) if collisions.any? 47 | end 48 | 49 | private 50 | 51 | def merged 52 | @merged ||= @a + @b 53 | end 54 | 55 | def raise_already_exists!(keys) 56 | raise ToFactory::AlreadyExists, "an item for each of the following keys #{keys.inspect} already exists" 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/to_factory/config.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | class << self 3 | attr_accessor :factories, :models 4 | 5 | def reset_config! 6 | @factories = nil 7 | @models = nil 8 | end 9 | 10 | def factories 11 | @factories ||= './spec/factories' 12 | end 13 | 14 | def models 15 | @models ||= './app/models' 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/to_factory/file_sync.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | class FileSync 3 | def initialize(m = ToFactory::Finders::Model.new, 4 | fw = ToFactory::FileWriter.new, 5 | ff = ToFactory::Finders::Factory.new) 6 | @model_finder = wrap_model(m) 7 | @file_writer = fw 8 | @factory_finder = ff 9 | end 10 | 11 | def perform(exclusions = []) 12 | @file_writer.write(all_representations(exclusions)) 13 | end 14 | 15 | def all_representations(exclusions = []) 16 | Collation.organize( 17 | new_representations(exclusions), 18 | existing_representations 19 | ) 20 | end 21 | 22 | def new_representations(exclusions = []) 23 | instances = @model_finder.call(exclusions: exclusions) 24 | 25 | instances.map { |r| Representation.from(r) } 26 | end 27 | 28 | private 29 | 30 | def existing_representations 31 | @factory_finder.call 32 | end 33 | 34 | def wrap_model(m) 35 | return m if m.respond_to?(:call) 36 | 37 | lambda do |exclusions = []| 38 | records = if m.is_a?(ActiveRecord::Base) 39 | Array m 40 | else 41 | m 42 | end 43 | 44 | records.reject { |o, _| exclusions.include?(o.class) } 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/to_factory/file_writer.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | class FileWriter 3 | def initialize 4 | FileUtils.mkdir_p(ToFactory.factories) 5 | end 6 | 7 | def write(definitions) 8 | definitions.each do |klass_name, representations| 9 | write_to(klass_name) do 10 | wrap_factories(representations.map(&:definition)) 11 | end 12 | end 13 | end 14 | 15 | private 16 | 17 | def write_to(name) 18 | mkdir(name) 19 | 20 | File.open(File.join(ToFactory.factories, "#{name}.rb"), 'w') do |f| 21 | f << yield 22 | end 23 | end 24 | 25 | def wrap_factories(definitions) 26 | out = "FactoryBot.define do\n" 27 | out << indent(definitions).join("\n\n") 28 | out << "\n" unless out[-1] == "\n" 29 | out << 'end' 30 | out << "\n" unless out[-1] == "\n" 31 | end 32 | 33 | def indent(definitions) 34 | definitions.map do |d| 35 | if d[/\A\s\s/] 36 | d 37 | else 38 | lines = d.split("\n").map do |l| 39 | " #{l}" 40 | end 41 | 42 | lines.join("\n") 43 | end 44 | end 45 | end 46 | 47 | def mkdir(name) 48 | return unless name.to_s['/'] 49 | 50 | dir = name.to_s.split('/')[0..-2] 51 | FileUtils.mkdir_p File.join(ToFactory.factories, dir) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/to_factory/finders/factory.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | module Finders 3 | class Factory 4 | def call 5 | all = [] 6 | 7 | parsed_files.each do |r| 8 | all = Collation.representations_from(all, r) 9 | end 10 | 11 | all 12 | end 13 | 14 | private 15 | 16 | def parsed_files 17 | Dir.glob(File.join(ToFactory.factories, '**/*.rb')).map do |f| 18 | parse_file(f) 19 | end.compact 20 | end 21 | 22 | def parse_file(f) 23 | ToFactory::Parsing::File.parse(f) 24 | rescue ToFactory::Parsing::File::EmptyFileException => e 25 | # ignore empty files 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/to_factory/finders/model.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | module Finders 3 | class Model 4 | def call(exclusions: [], klasses: nil) 5 | instances = [] 6 | klasses ||= find_klasses(exclusions) 7 | 8 | klasses.each do |klass| 9 | if instance = get_active_record_instance(klass) 10 | instances << instance 11 | end 12 | end 13 | 14 | instances 15 | end 16 | 17 | private 18 | 19 | def find_klasses(exclusions) 20 | klasses = [] 21 | models.each do |file| 22 | matching_lines(file) do |match| 23 | klass = rescuing_require(file, match) 24 | 25 | klasses << klass unless exclusions.include?(klass) 26 | end 27 | end 28 | 29 | klasses 30 | end 31 | 32 | def models 33 | Dir.glob("#{ToFactory.models}/**/*.rb") 34 | end 35 | 36 | def matching_lines(file) 37 | File.readlines(file).each do |l| 38 | if match = l.match(/class (.*) ? e 48 | warn "Failed to eval #{file}" 49 | warn e.message 50 | end 51 | 52 | def get_active_record_instance(klass) 53 | klass.first if klass && klass.ancestors.include?(ActiveRecord::Base) 54 | rescue StandardError => e 55 | warn "Failed to get record from #{klass} #{e.message.inspect}" 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/to_factory/generation/attribute.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | module Generation 3 | class Attribute 4 | attr_writer :parser 5 | 6 | def initialize(attribute, value) 7 | @attribute = attribute 8 | @value = value 9 | end 10 | 11 | def to_s 12 | setter = "#{@attribute}#{inspect_value(@value)}" 13 | " #{setter}" 14 | end 15 | 16 | def inspect_value(value, nested = false) 17 | formatted = format(value, nested) 18 | 19 | formatted = " { #{formatted} }" if !value.is_a?(Hash) && !nested 20 | 21 | formatted 22 | end 23 | 24 | def format(value, nested = false) 25 | case value 26 | when Time, DateTime 27 | inspect_time(value) 28 | when Date 29 | value.to_s.inspect 30 | when BigDecimal 31 | "BigDecimal.new(#{value.to_s.inspect})" 32 | when Hash 33 | inspect_hash(value, nested) 34 | when Array 35 | inspect_array(value, nested) 36 | when String 37 | validate_parseable!(value).inspect 38 | else 39 | value.inspect 40 | end 41 | end 42 | 43 | private 44 | 45 | def validate_parseable!(value) 46 | return value if parse(value) 47 | 48 | 'ToFactory: RubyParser exception parsing this attribute' 49 | end 50 | 51 | def parse(value) 52 | @parser ||= RubyParser.new 53 | @parser.parse(value) 54 | true 55 | rescue StandardError 56 | false 57 | end 58 | 59 | def inspect_time(value) 60 | value = value.strftime('%Y-%m-%dT%H:%M %Z').inspect 61 | value.gsub!(/GMT/, 'UTC') 62 | value 63 | end 64 | 65 | def inspect_hash(value, nested) 66 | formatted = value.keys.inject([]) do |a, key| 67 | a << key_value_pair(key, value) 68 | end.join(', ') 69 | 70 | if nested 71 | "{#{formatted}}" 72 | else 73 | " { {#{formatted}} }" 74 | end 75 | end 76 | 77 | def inspect_array(value, nested) 78 | values = value.map { |v| format(v, nested) }.join(', ') 79 | "[#{values}]" 80 | end 81 | 82 | def key_value_pair(key, value) 83 | formatted_key = inspect_value(key, true) 84 | formatted_value = inspect_value(value.fetch(key), true) 85 | "#{formatted_key} => #{formatted_value}" 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/to_factory/generation/factory.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | module Generation 3 | class Factory 4 | def initialize(representation) 5 | @representation = representation 6 | end 7 | 8 | def name 9 | add_quotes @representation.name 10 | end 11 | 12 | def to_factory 13 | header do 14 | attributes.map do |attr, value| 15 | factory_attribute(attr, value) 16 | end.sort.join("\n") << "\n" 17 | end 18 | end 19 | 20 | def header(&block) 21 | generic_header(' factory', '', ' end', &block) 22 | end 23 | 24 | def factory_attribute(attr, value) 25 | Attribute.new(attr, value).to_s 26 | end 27 | 28 | def attributes 29 | to_skip = %i[id created_at created_on updated_at updated_on] 30 | 31 | @representation.attributes.delete_if { |key, _| key.nil? || to_skip.include?(key.to_sym) } 32 | end 33 | 34 | private 35 | 36 | def parent_name 37 | @representation.parent_name 38 | end 39 | 40 | def generic_header(factory_start, block_arg, ending, &_block) 41 | out = "#{factory_start}(:#{name}#{parent_clause}) do#{block_arg}\n" 42 | out << yield.to_s 43 | out << "#{ending}\n" 44 | end 45 | 46 | def parent_clause 47 | has_parent? ? ", :parent => :#{add_quotes parent_name}" : '' 48 | end 49 | 50 | def has_parent? 51 | parent_name.to_s.length > 0 52 | end 53 | 54 | def add_quotes(name) 55 | name = name.to_s 56 | 57 | if name['/'] 58 | if name[/^".*"$/] 59 | name 60 | else 61 | "\"#{name}\"" 62 | end 63 | else 64 | name 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/to_factory/klass_inference.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | class CannotInferClass < ArgumentError 3 | def initialize(message) 4 | super message.inspect 5 | end 6 | end 7 | 8 | class KlassInference 9 | def initialize(representations) 10 | @mapping = {} 11 | 12 | representations.each do |r| 13 | set_mapping_from(r) 14 | end 15 | end 16 | 17 | def infer(factory_name, count = 0) 18 | count += 1 19 | result = @mapping[factory_name] 20 | return [result, count] if result.is_a? Class 21 | 22 | raise CannotInferClass, factory_name if result.nil? 23 | 24 | infer(result, count) 25 | end 26 | 27 | private 28 | 29 | def set_mapping_from(r) 30 | if parent_klass = to_const(r.parent_name) 31 | @mapping[r.parent_name] = parent_klass 32 | end 33 | 34 | @mapping[r.name] = 35 | if factory_klass = to_const(r.name) 36 | factory_klass 37 | else 38 | r.parent_name 39 | end 40 | end 41 | 42 | def to_const(factory_name) 43 | factory_name.to_s.camelize.constantize 44 | rescue NameError 45 | nil 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/to_factory/options_parser.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | class OptionsParser 3 | def initialize(options) 4 | @options = options 5 | end 6 | 7 | def get_instance 8 | args = case @options 9 | when ActiveRecord::Base 10 | from_record(@options) 11 | when Array 12 | from_array(*@options) 13 | end 14 | 15 | Representation.new(*args) 16 | end 17 | 18 | def from_record(record) 19 | name = calculate_name record.class 20 | 21 | [name, nil, nil, record] 22 | end 23 | 24 | def from_array(name, record) 25 | parent_name = calculate_name(record.class) 26 | parent_name = nil if parent_name.to_s == name.to_s 27 | [name, parent_name, nil, record] 28 | end 29 | 30 | def calculate_name(klass) 31 | klass.name.to_s.underscore 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/to_factory/parsing/file.rb: -------------------------------------------------------------------------------- 1 | require 'ruby2ruby' 2 | require 'ruby_parser' 3 | require 'to_factory/parsing/ruby_parsing_helpers' 4 | 5 | module ToFactory 6 | module Parsing 7 | ParseException = Class.new ::StandardError 8 | 9 | class CouldNotInferClassException < ParseException 10 | attr_reader :sexp 11 | 12 | def initialize(sexp) 13 | @sexp = sexp 14 | end 15 | end 16 | 17 | class File 18 | EmptyFileException = Class.new ArgumentError 19 | 20 | include Parsing::RubyParsingHelpers 21 | attr_accessor :contents 22 | 23 | class << self 24 | def parse(filename) 25 | from_file(filename).parse 26 | end 27 | 28 | def from_file(filename) 29 | begin 30 | contents = ::File.read filename 31 | rescue StandardError 32 | nil 33 | end 34 | raise EmptyFileException, "Invalid file #{filename}" if contents.to_s.strip.length == 0 35 | 36 | new(contents) 37 | end 38 | end 39 | 40 | def initialize(contents) 41 | @contents = contents 42 | end 43 | 44 | def multiple_factories? 45 | factories_sexp[0] == :block 46 | end 47 | 48 | def parse 49 | factories.map do |x| 50 | representation_from(x) 51 | end 52 | rescue Racc::ParseError, StringScanner::Error => e 53 | raise ParseException, 54 | "Original exception: #{e.message}\n #{e.backtrace.join("\n")}\nToFactory Error parsing \n#{@contents}\n o" 55 | end 56 | 57 | def header? 58 | sexp[1][1][1] == :FactoryBot 59 | rescue StandardError 60 | false 61 | end 62 | 63 | private 64 | 65 | def representation_from(x) 66 | Representation.new(name_from(x), parent_from(x), to_ruby(x)) 67 | rescue CouldNotInferClassException => e 68 | ruby = to_ruby(e.sexp) 69 | Kernel.warn "ToFactory could not parse\n#{ruby}" 70 | NullRepresentation.new(e.sexp) 71 | end 72 | 73 | def factories 74 | if multiple_factories? 75 | factories_sexp[1..-1] 76 | else 77 | [factories_sexp] 78 | end 79 | end 80 | 81 | def factories_sexp 82 | header? ? sexp[3] : sexp 83 | end 84 | 85 | def name_from(sexp) 86 | sexp[1][3][1] 87 | rescue NoMethodError 88 | raise CouldNotInferClassException, sexp 89 | end 90 | 91 | def parent_from(x) 92 | # e.g. 93 | # s(:call, nil, :factory, s(:lit, :admin), s(:hash, s(:lit, :parent), s(:lit, :"to_factory/user"))) 94 | x[1][4][2][1] 95 | rescue NoMethodError 96 | # e.g. 97 | # s(:call, nil, :factory, s(:lit, :"to_factory/user")) 98 | x[1][3][1] 99 | end 100 | 101 | def sexp 102 | @sexp ||= ruby_parser.process(@contents) 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/to_factory/parsing/ruby_parsing_helpers.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | module Parsing 3 | module RubyParsingHelpers 4 | def to_ruby(sexp) 5 | ruby2ruby.process sexp.deep_clone 6 | end 7 | 8 | def ruby2ruby 9 | @ruby2ruby ||= Ruby2Ruby.new 10 | end 11 | 12 | def ruby_parser 13 | @ruby_parseer ||= RubyParser.new 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/to_factory/representation.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | class NullRepresentation 3 | include ToFactory::Parsing::RubyParsingHelpers 4 | 5 | def initialize(sexp) 6 | @sexp = sexp 7 | end 8 | 9 | def name 10 | nil 11 | end 12 | 13 | def parent 14 | nil 15 | end 16 | 17 | def definition 18 | to_ruby(@sexp) 19 | end 20 | end 21 | 22 | class Representation 23 | delegate :attributes, to: :record 24 | attr_accessor :klass, :name, :parent_name, :definition, :hierarchy_order, :record 25 | 26 | def self.from(options) 27 | OptionsParser.new(options).get_instance 28 | end 29 | 30 | def initialize(name, parent_name, definition = nil, record = nil) 31 | @name = name.to_s 32 | @parent_name = parent_name.to_s 33 | @definition = definition 34 | @record = record 35 | end 36 | 37 | def inspect 38 | "#" 39 | end 40 | 41 | def klass_name_inspect 42 | @klass.name.inspect 43 | rescue StandardError 44 | 'nil' 45 | end 46 | 47 | def definition 48 | @definition ||= ToFactory::Generation::Factory.new(self).to_factory 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/to_factory/version.rb: -------------------------------------------------------------------------------- 1 | module ToFactory 2 | VERSION = '3.0.0-pre' 3 | end 4 | -------------------------------------------------------------------------------- /spec/db/migrate/1_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[6.1] 2 | def up 3 | create_table :users do |t| 4 | t.column :name, :string, null: false 5 | t.column :email, :string 6 | t.integer :some_id 7 | end 8 | end 9 | 10 | def down 11 | drop_table :users 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/db/migrate/2_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration[6.1] 2 | def up 3 | create_table :projects do |t| 4 | t.column :name, :string, null: false 5 | t.column :objective, :string 6 | t.integer :some_id 7 | end 8 | end 9 | 10 | def down 11 | drop_table :projects 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/db/migrate/3_create_not_namespaced.rb: -------------------------------------------------------------------------------- 1 | class CreateNotNamespaced < ActiveRecord::Migration[6.1] 2 | def up 3 | create_table :not_namespaced_active_record_class_but_long_enough_it_shouldnt_cause_conflicts do |t| 4 | t.column :name, :string, null: false 5 | end 6 | end 7 | 8 | def down 9 | drop_table :not_namespaced_active_record_class_but_long_enough_it_shouldnt_cause_conflicts 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/db/migrate/4_add_birthday_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddBirthdayToUsers < ActiveRecord::Migration[6.1] 2 | def up 3 | add_column :users, :birthday, :datetime 4 | end 5 | 6 | def down 7 | remove_column :users, :birthday 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/db/migrate/5_add_serialized_attributes_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddSerializedAttributesToUsers < ActiveRecord::Migration[6.1] 2 | def up 3 | add_column :users, :some_attributes, :text 4 | end 5 | 6 | def down 7 | remove_column :users, :some_attributes 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/example_factories/admin.rb: -------------------------------------------------------------------------------- 1 | factory(:admin, parent: :"to_factory/user") do 2 | birthday { "2014-07-08T15:30 UTC" } 3 | email {"admin@example.com"} 4 | name {"Admin"} 5 | some_attributes {{a: 1}} 6 | some_id { 9 } 7 | end 8 | -------------------------------------------------------------------------------- /spec/example_factories/admin_with_header.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:admin, parent: :"to_factory/user") do 3 | birthday { "2014-07-08T15:30 UTC" } 4 | email { "admin@example.com" } 5 | name {"Admin" } 6 | some_attributes { {a: 1 } } 7 | some_id { 9 } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/example_factories/inherited_project_with_header.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:"to_factory/inherited_project") do 3 | name { "My Project" } 4 | objective { "easy testing" } 5 | some_id { 9 } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/example_factories/project_with_header.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:"to_factory/project") do 3 | name { "My Project" } 4 | objective { "easy testing" } 5 | some_id { 9 } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/example_factories/user.rb: -------------------------------------------------------------------------------- 1 | factory(:"to_factory/user") do 2 | birthday {"2014-07-08T15:30 UTC"} 3 | email {"test@example.com"} 4 | name {"Jeff"} 5 | some_attributes {{a: 1}} 6 | some_id { 8 } 7 | end 8 | -------------------------------------------------------------------------------- /spec/example_factories/user_admin.rb: -------------------------------------------------------------------------------- 1 | factory(:"to_factory/user") do 2 | birthday { "2014-07-08T15:30 UTC" } 3 | email { "test@example.com" } 4 | name { "Jeff" } 5 | some_attributes { {a: 1 } } 6 | some_id { 8 } 7 | end 8 | 9 | factory(:admin, parent: :"to_factory/user") do 10 | birthday {"2014-07-08T15:30 UTC"} 11 | email {"admin@example.com"} 12 | name {"Admin"} 13 | some_attributes { {a: 1 } } 14 | some_id { 9 } 15 | end 16 | -------------------------------------------------------------------------------- /spec/example_factories/user_admin_root.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:"to_factory/user") do 3 | name {"User"} 4 | end 5 | 6 | factory(:admin, parent: :"to_factory/user") do 7 | name {"Admin"} 8 | end 9 | 10 | factory(:root, parent: :"to_factory/user") do 11 | birthday {"2014-07-08T15:30 UTC"} 12 | email {"test@example.com"} 13 | name {"Jeff"} 14 | some_attributes {{a: 1}} 15 | some_id { 8 } 16 | end 17 | 18 | factory(:super_admin, parent: :admin) do 19 | name {"Super Admin"} 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/example_factories/user_admin_super_admin.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:"to_factory/user") do 3 | name {"User"} 4 | end 5 | 6 | factory(:admin, parent: :"to_factory/user") do 7 | name {"Admin" } 8 | end 9 | 10 | factory(:super_admin, parent: :admin) do 11 | name {"Super Admin"} 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/example_factories/user_admin_with_header.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:"to_factory/user") do 3 | birthday {"2014-07-08T15:30 UTC"} 4 | email {"test@example.com"} 5 | name {"Jeff"} 6 | some_attributes {{a: 1}} 7 | some_id { 8 } 8 | end 9 | 10 | factory(:admin, parent: :"to_factory/user") do 11 | birthday {"2014-07-08T15:30 UTC"} 12 | email {"admin@example.com"} 13 | name {"Admin"} 14 | some_attributes {{a: 1}} 15 | some_id { 9 } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/example_factories/user_with_header.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory(:"to_factory/user") do 3 | birthday { "2014-07-08T15:30 UTC" } 4 | email { "test@example.com" } 5 | name { "Jeff" } 6 | some_attributes { {:a => 1} } 7 | some_id { 8 } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/integration/config_spec.rb: -------------------------------------------------------------------------------- 1 | describe "ToFactory Configuration" do 2 | before do 3 | ToFactory.reset_config! 4 | end 5 | 6 | it do 7 | expect(ToFactory.factories).to eq "./spec/factories" 8 | ToFactory.factories = "factories dir" 9 | 10 | expect(ToFactory.factories).to eq "factories dir" 11 | end 12 | 13 | it do 14 | expect(ToFactory.models).to eq "./app/models" 15 | ToFactory.models = "models dir" 16 | 17 | expect(ToFactory.models).to eq "models dir" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/integration/empty_factory_file_spec.rb: -------------------------------------------------------------------------------- 1 | describe "Empty File spec" do 2 | let(:user) { create_user! } 3 | 4 | context "with an empty factory file" do 5 | before do 6 | user 7 | FileUtils.mkdir_p "./tmp/factories/to_factory" 8 | 9 | File.open("./tmp/factories/to_factory/empty.rb", "w") do |f| 10 | f << " " 11 | f << "\n " 12 | end 13 | end 14 | 15 | it "doesn't blow up" do 16 | ToFactory(empty: OpenStruct.new({ attributes: {} }), user: user) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/integration/file_sync_spec.rb: -------------------------------------------------------------------------------- 1 | describe "FileSync" do 2 | let(:user) { create_user! } 3 | let(:admin) { create_admin! } 4 | let(:project) { create_project! } 5 | let(:expected_user_file) { File.read("./spec/example_factories/user.rb") } 6 | let(:expected_user_with_header_file) { File.read("./spec/example_factories/user_with_header.rb") } 7 | let(:expected_admin_file) { File.read("./spec/example_factories/admin.rb") } 8 | let(:user_with_header) { File.read("./spec/example_factories/user_with_header.rb") } 9 | let(:user_admin_with_header) { File.read("./spec/example_factories/user_admin_with_header.rb") } 10 | 11 | def user_file 12 | File.read("./tmp/factories/to_factory/user.rb") 13 | rescue 14 | nil 15 | end 16 | 17 | def project_file 18 | File.read("./tmp/factories/to_factory/project.rb") 19 | rescue 20 | nil 21 | end 22 | 23 | context "with no arguments" do 24 | before do 25 | user 26 | admin 27 | end 28 | 29 | it "finds the first existing instance" do 30 | sync = ToFactory::FileSync.new 31 | sync.perform 32 | 33 | expect(user_file).to match_sexp expected_user_with_header_file 34 | end 35 | end 36 | 37 | context "with an instance" do 38 | it "writes that instance" do 39 | sync = ToFactory::FileSync.new(user) 40 | sync.perform 41 | 42 | expect(user_file).to match_sexp user_with_header 43 | expect(project_file).to eq nil 44 | end 45 | end 46 | 47 | context "with a pre-existing file" do 48 | let(:sync) { ToFactory::FileSync.new(user) } 49 | before do 50 | sync.perform 51 | expect(user_file).to match_sexp user_with_header 52 | end 53 | 54 | it "raises an error" do 55 | expect { sync.perform }.to raise_error ToFactory::AlreadyExists 56 | end 57 | 58 | context "with a named factory" do 59 | it do 60 | sync = ToFactory::FileSync.new(admin: admin) 61 | sync.perform 62 | 63 | parser = ToFactory::Parsing::File.new(user_file) 64 | result = parser.parse 65 | admin = result.find { |r| r.name == "admin" } 66 | user = result.find { |r| r.name == "to_factory/user" } 67 | 68 | expect(admin.definition).to match_sexp expected_admin_file 69 | expect(user.definition).to match_sexp expected_user_file 70 | 71 | expect { sync.perform }.to raise_error ToFactory::AlreadyExists 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/integration/file_writer_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::FileWriter do 2 | let(:file_writer) { ToFactory::FileWriter.new } 3 | let(:user_file_contents) { File.read "./tmp/factories/to_factory/user.rb" } 4 | let!(:user) { create_user! } 5 | let!(:admin) { create_admin! } 6 | let(:file_sync) { ToFactory::FileSync.new } 7 | let(:representations) { file_sync.all_representations } 8 | 9 | let(:expected) { File.read "./spec/example_factories/user_with_header.rb" } 10 | 11 | before do 12 | # sanity check generation isn't broken 13 | expect(representations.keys).to eq ["to_factory/user"] 14 | expect(representations.values[0][0]).to be_a ToFactory::Representation 15 | end 16 | 17 | it 'writes correctly' do 18 | file_writer.write representations 19 | 20 | expect(user_file_contents).to match_sexp expected 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/lint_spec.rb: -------------------------------------------------------------------------------- 1 | context "full integration" do 2 | let(:user) do 3 | ToFactory::User.create name: "Jeff", email: "test@example.com", some_id: 8, birthday: Time.now 4 | end 5 | 6 | it "#to_factory linting the output" do 7 | ToFactory(user) 8 | require 'factory_bot' 9 | 10 | load "./tmp/factories/to_factory/user.rb" 11 | FactoryBot.lint 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/integration/multiple_to_factory_calls_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory do 2 | let!(:user) { create_user! } 3 | 4 | def user_file 5 | File.read("./tmp/factories/to_factory/user.rb") 6 | rescue 7 | nil 8 | end 9 | 10 | let(:expected_user_file) { File.read "./spec/example_factories/user_with_header.rb" } 11 | 12 | context "single call" do 13 | before do 14 | ToFactory() 15 | end 16 | it "renders a single factory correctly" do 17 | expect(user_file).to match_sexp expected_user_file 18 | end 19 | end 20 | 21 | context "two calls" do 22 | before do 23 | ToFactory() 24 | ToFactory(user_2: ToFactory::User.first) 25 | end 26 | 27 | it "renders two factories correctly" do 28 | expect(user_file).to eq <<-FACTORY.strip_heredoc 29 | FactoryBot.define do 30 | factory(:"to_factory/user") do 31 | birthday { "2014-07-08T15:30 UTC" } 32 | email { "test@example.com" } 33 | name { "Jeff" } 34 | some_attributes { { :a => 1 } } 35 | some_id { 8 } 36 | end 37 | 38 | factory(:user_2, :parent => :"to_factory/user") do 39 | birthday { "2014-07-08T15:30 UTC" } 40 | email { "test@example.com" } 41 | name { "Jeff" } 42 | some_attributes { {:a => 1} } 43 | some_id { 8 } 44 | end 45 | end 46 | FACTORY 47 | end 48 | end 49 | 50 | context "multiple calls" do 51 | before do 52 | ToFactory() 53 | ToFactory(user_2: ToFactory::User.first) 54 | ToFactory(user_3: ToFactory::User.first) 55 | ToFactory(user_4: ToFactory::User.first) 56 | end 57 | 58 | it "renders multiple factories correctly" do 59 | expect(user_file).to match_sexp <<-FACTORY 60 | FactoryBot.define do 61 | factory(:"to_factory/user") do 62 | birthday { "2014-07-08T15:30 UTC" } 63 | email { "test@example.com" } 64 | name { "Jeff" } 65 | some_attributes { {:a => 1} } 66 | some_id { 8 } 67 | end 68 | 69 | factory(:user_2, :parent => :"to_factory/user") do 70 | birthday { "2014-07-08T15:30 UTC" } 71 | email { "test@example.com" } 72 | name { "Jeff" } 73 | some_attributes { {:a => 1} } 74 | some_id { 8 } 75 | end 76 | 77 | factory(:user_3, :parent => :"to_factory/user") do 78 | birthday { "2014-07-08T15:30 UTC" } 79 | email { "test@example.com" } 80 | name { "Jeff" } 81 | some_attributes { {:a => 1} } 82 | some_id { 8 } 83 | end 84 | 85 | factory(:user_4, :parent => :"to_factory/user") do 86 | birthday { "2014-07-08T15:30 UTC" } 87 | email { "test@example.com" } 88 | name { "Jeff" } 89 | some_attributes { {:a => 1} } 90 | some_id { 8 } 91 | end 92 | end 93 | FACTORY 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/integration/non_active_record_classes_spec.rb: -------------------------------------------------------------------------------- 1 | describe "non active record classes" do 2 | let!(:project) { create_project! } 3 | 4 | def project_file 5 | File.read("./tmp/factories/to_factory/project.rb") 6 | rescue 7 | nil 8 | end 9 | 10 | def inherited_project_file 11 | File.read("./tmp/factories/to_factory/inherited_project.rb") 12 | rescue 13 | nil 14 | end 15 | 16 | let(:expected_project_file) { File.read "./spec/example_factories/project_with_header.rb" } 17 | let(:expected_inherited_project_file) { File.read "./spec/example_factories/inherited_project_with_header.rb" } 18 | 19 | context "given a folder with non active record models in" do 20 | before do 21 | ToFactory.models = "./spec/support/non_active_record" 22 | ToFactory() 23 | end 24 | 25 | it "creates an ordinary factory correctly" do 26 | expect(project_file).to match_sexp expected_project_file 27 | end 28 | 29 | context "STI" do 30 | it "creates a single factory correctly" do 31 | expect(inherited_project_file).to match_sexp expected_inherited_project_file 32 | end 33 | end 34 | 35 | it "doesn't create other factories" do 36 | count = Dir.glob("./tmp/factories/*.rb").length 37 | expect(count).to eq 0 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/integration/to_factory_method_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory do 2 | let!(:user) { create_user! } 3 | let!(:project) { create_project! } 4 | 5 | def user_file 6 | File.read("./tmp/factories/to_factory/user.rb") 7 | rescue 8 | nil 9 | end 10 | 11 | def project_file 12 | File.read("./tmp/factories/to_factory/project.rb") 13 | rescue 14 | nil 15 | end 16 | 17 | let(:expected_user_file) { File.read "./spec/example_factories/user_with_header.rb" } 18 | let(:expected_project_file) { File.read "./spec/example_factories/project_with_header.rb" } 19 | 20 | describe "ToFactory.definitions" do 21 | it do 22 | ToFactory() 23 | expect(ToFactory.definitions).to match_array ["to_factory/user", "to_factory/project"] 24 | end 25 | end 26 | 27 | describe "ToFactory.definition_for" do 28 | let(:expected_user_file) { File.read "./spec/example_factories/user.rb" } 29 | it do 30 | expect(ToFactory.definition_for user).to match_sexp expected_user_file 31 | end 32 | 33 | it do 34 | ToFactory(user) 35 | expect(ToFactory.definition_for :"to_factory/user").to match_sexp expected_user_file 36 | end 37 | 38 | it "raises a not found error" do 39 | expect { ToFactory.definition_for :"to_factory/user" }.to raise_error ToFactory::NotFoundError 40 | end 41 | end 42 | 43 | describe "Object#ToFactory" do 44 | context "with multiple levels of parent classes" do 45 | let(:filename) { "spec/example_factories/#{'user_admin_super_admin'}.rb" } 46 | 47 | it "gets the output order correct" do 48 | output = "./tmp/factories/to_factory/user.rb" 49 | `mkdir -p ./tmp/factories/to_factory` 50 | `cp #{filename} #{output}` 51 | 52 | ToFactory(root: user) 53 | 54 | expected = File.read "spec/example_factories/#{'user_admin_root'}.rb" 55 | 56 | # user, admin, super_admin, root 57 | expect(File.read(output)).to match_sexp expected 58 | end 59 | end 60 | 61 | it "generates all factories" do 62 | ToFactory() 63 | # simple check for equivalent ruby 64 | expect(user_file) .to match_sexp expected_user_file 65 | expect(project_file).to match_sexp expected_project_file 66 | 67 | # once we are sure output is equivalent ruby, check output is identical 68 | expect(user_file.chomp) .to eq expected_user_file.chomp 69 | expect(project_file.chomp).to eq expected_project_file.chomp 70 | end 71 | 72 | def user_file_includes(content) 73 | expect(user_file).to include content 74 | end 75 | 76 | context "excluding classes" do 77 | before do 78 | user 79 | project 80 | end 81 | 82 | it "ignores specified classes" do 83 | ToFactory(exclude: ToFactory::User) 84 | expect(user_file).to be_nil 85 | expect(project_file).to be_present 86 | end 87 | 88 | it "ignores specified classes - sanity check" do 89 | ToFactory(exclude: ToFactory::Project) 90 | expect(user_file).to be_present 91 | expect(project_file).to be_nil 92 | end 93 | end 94 | 95 | context "with no existing file" do 96 | it "creates the file" do 97 | expect(user_file).to be_nil 98 | ToFactory(user) 99 | expect(user_file).to be_present 100 | end 101 | 102 | context "with single ActiveRecord::Base instance argument" do 103 | it "creates the file" do 104 | expect(user_file).to be_nil 105 | ToFactory(user) 106 | expect(user_file).to be_present 107 | end 108 | end 109 | end 110 | 111 | context "with an existing file" do 112 | before do 113 | expect(user_file).to be_nil 114 | ToFactory(user) 115 | expect(user_file).to be_present 116 | end 117 | 118 | context "with a name for the factory" do 119 | it "appends to the file" do 120 | user_file_includes('factory(:"to_factory/user"') 121 | ToFactory(specific_user: user) 122 | user_file_includes('factory(:specific_user, :parent => :"to_factory/user"') 123 | end 124 | end 125 | 126 | it "without a name" do 127 | expect { ToFactory(user) } 128 | .to raise_error ToFactory::AlreadyExists 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'simplecov' 3 | SimpleCov.start 4 | rescue LoadError 5 | # ignore on ruby 1.8.x 6 | end 7 | 8 | require "active_record" 9 | require "active_support/core_ext/string" 10 | require "active_support/core_ext/hash" 11 | require "sqlite3" 12 | require "database_cleaner" 13 | 14 | require "byebug" 15 | require "to_factory" 16 | 17 | require "./spec/support/models/user" 18 | require "./spec/support/models/project" 19 | require "./spec/support/match_sexp" 20 | require "./spec/support/data_creation" 21 | 22 | RSpec.configure do |config| 23 | config.include ToFactory::DataCreation 24 | 25 | config.before :suite do 26 | ActiveRecord::Base.tap do |base| 27 | config = { adapter: "sqlite3", database: "spec/db/test.sqlite3" } 28 | base.configurations = { test: config }.with_indifferent_access 29 | base.establish_connection :test 30 | end 31 | 32 | DatabaseCleaner.strategy = :truncation 33 | DatabaseCleaner.clean_with(:truncation) 34 | DatabaseCleaner.start 35 | end 36 | 37 | config.before(:each) do 38 | FileUtils.rm_rf "tmp/factories" 39 | DatabaseCleaner.clean 40 | ToFactory.reset_config! 41 | ToFactory.models = "./spec/support/models" 42 | ToFactory.factories = "./tmp/factories" 43 | end 44 | 45 | config.expect_with :rspec do |expectations| 46 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 47 | end 48 | 49 | config.mock_with :rspec do |mocks| 50 | mocks.verify_partial_doubles = true 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/support/broken_models/invalid_ruby_file.rb: -------------------------------------------------------------------------------- 1 | class Egg < ActiveRecord::Base 2 | -------------------------------------------------------------------------------- /spec/support/broken_models/project.rb: -------------------------------------------------------------------------------- 1 | class ToFactory::Project < ActiveRecord::Base 2 | self.table_name = "projects" 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/data_creation.rb: -------------------------------------------------------------------------------- 1 | module ToFactory::DataCreation 2 | def create_all! 3 | create_user! 4 | create_admin! 5 | end 6 | 7 | def create_project! 8 | ToFactory::Project.create(name: "My Project", objective: "easy testing", some_id: 9) 9 | end 10 | 11 | def birthday 12 | Time.find_zone("UTC").parse("2014-07-08T15:30Z") 13 | end 14 | 15 | def create_user! 16 | ToFactory::User.create( 17 | name: "Jeff", 18 | email: "test@example.com", 19 | some_attributes: { a: 1 }, 20 | some_id: 8, 21 | birthday: birthday 22 | ) 23 | end 24 | 25 | def create_admin! 26 | ToFactory::User.create( 27 | name: "Admin", 28 | email: "admin@example.com", 29 | some_attributes: { a: 1 }, 30 | some_id: 9, 31 | birthday: birthday 32 | ) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/support/match_sexp.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :match_sexp do |expected| 2 | match do |actual| 3 | parser = RubyParser.new 4 | 5 | begin 6 | parser.process(expected) == parser.process(actual) 7 | rescue Exception => e 8 | @raised = e 9 | false 10 | end 11 | end 12 | 13 | failure_message do |actual| 14 | message = "expected \n#{actual}\n\nto match expected sexp of\n\n#{expected}" 15 | 16 | if @raised 17 | message << "\nbut raised\n" 18 | message << @raised.message 19 | else 20 | message 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/models/not_active_record.rb: -------------------------------------------------------------------------------- 1 | class ToFactory::NotActiveRecord 2 | end 3 | -------------------------------------------------------------------------------- /spec/support/models/project.rb: -------------------------------------------------------------------------------- 1 | class ToFactory::Project < ActiveRecord::Base 2 | self.table_name = "projects" 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/models/user.rb: -------------------------------------------------------------------------------- 1 | class ToFactory::User < ActiveRecord::Base 2 | self.table_name = "users" 3 | serialize :some_attributes, Hash 4 | end 5 | -------------------------------------------------------------------------------- /spec/support/non_active_record/inherited_project.rb: -------------------------------------------------------------------------------- 1 | class ToFactory::InheritedProject < ToFactory::Project 2 | self.table_name = "projects" 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/non_active_record/project.rb: -------------------------------------------------------------------------------- 1 | class ToFactory::Project < ActiveRecord::Base 2 | self.table_name = "projects" 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/non_active_record/some_other_service_inheriting_from_something_else.rb: -------------------------------------------------------------------------------- 1 | require_relative "something_else" 2 | 3 | class SomeOtherService < SomethingElse 4 | end 5 | -------------------------------------------------------------------------------- /spec/support/non_active_record/some_service.rb: -------------------------------------------------------------------------------- 1 | class SomeService 2 | end 3 | -------------------------------------------------------------------------------- /spec/support/non_active_record/something_else.rb: -------------------------------------------------------------------------------- 1 | class SomethingElse 2 | end 3 | -------------------------------------------------------------------------------- /spec/support/ruby_parser_exception_causing_string.rb: -------------------------------------------------------------------------------- 1 | " 2 | \ \ \ - \ \ 3 | 4 | 5 | , 6 | 7 | 8 | 9 | 10 | \ 11 | 12 | 13 | \ 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | , 24 | 25 | \ \ 26 | 27 | 28 | 29 | 30 | 31 | \ " 32 | -------------------------------------------------------------------------------- /spec/unit/collation_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::Collation do 2 | describe "detect_collisions!" do 3 | let(:collation) { ToFactory::Collation.new(a, b) } 4 | let(:a) { [double(name: "a")] } 5 | let(:b) { [double(name: "a")] } 6 | 7 | def perform 8 | collation.detect_collisions!(a, b) 9 | end 10 | 11 | it do 12 | expect { perform }.to raise_error ToFactory::AlreadyExists 13 | end 14 | 15 | context "non matching keys" do 16 | let(:a) { [double(name: "a")] } 17 | let(:b) { [double(name: "b")] } 18 | 19 | it do 20 | expect(perform).to eq nil 21 | end 22 | end 23 | end 24 | 25 | context "organizing" do 26 | let(:root) { ToFactory::Representation.new(:root, "super_admin", " Factory.define(:root, :parent => :\"to_factory/user\") do|o|\n o.birthday \"2014-07-08T15:30Z\"\n o.email \"test@example.com\"\n o.name \"Jeff\"\n o.some_id 8\n end\n") } 27 | let(:user) { ToFactory::Representation.new("to_factory/user", nil, "Factory.define(:\"to_factory/user\") { |o| o.name(\"User\") }") } 28 | let(:admin) { ToFactory::Representation.new("admin", "to_factory/user", "Factory.define(:admin, :parent => :\"to_factory/user\") { |o| o.name(\"Admin\") }") } 29 | let(:super_admin) { ToFactory::Representation.new("super_admin", "admin", "Factory.define(:super_admin, :parent => :admin) { |o| o.name(\"Super Admin\") }") } 30 | 31 | it do 32 | new_definitions = [root] 33 | pre_existing = [admin, user, super_admin] 34 | 35 | result = ToFactory::Collation.organize(new_definitions, pre_existing) 36 | result = result["to_factory/user"] 37 | expect(result.map &:hierarchy_order).to eq [1, 2, 3, 4] 38 | expect(result).to eq [user, admin, super_admin, root] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/unit/file_writer_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::FileWriter do 2 | let(:fw) { ToFactory::FileWriter.new } 3 | 4 | describe "#write" do 5 | def user_file 6 | File.read("tmp/factories/to_factory/user.rb") 7 | end 8 | 9 | def project_file 10 | File.read("tmp/factories/to_factory/project.rb") 11 | end 12 | 13 | it "adds factories for all models" do 14 | user_representation = double :name => :user, "definition" => "factory a" 15 | project_representation = double name: "project", definition: "factory b" 16 | fw.write("to_factory/user" => [user_representation], 17 | "to_factory/project" => [project_representation]) 18 | 19 | expect(user_file).to match /FactoryBot.define do/ 20 | expect(user_file).to include "factory a" 21 | expect(project_file).to include "factory b" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/finders/factory_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::Finders::Factory do 2 | describe "#call" do 3 | before do 4 | FileUtils.mkdir_p "./tmp/factories/to_factory" 5 | FileUtils.cp "./spec/example_factories/user_admin_with_header.rb", 6 | "./tmp/factories/to_factory/user.rb" 7 | end 8 | 9 | let(:user_file_contents) { File.read "./spec/example_factories/user.rb" } 10 | let(:admin_file_contents) { File.read "./spec/example_factories/admin.rb" } 11 | 12 | it "reads all the factories" do 13 | finder = ToFactory::Finders::Factory.new 14 | 15 | result = finder.call 16 | 17 | expect(result[0].definition) 18 | .to match_sexp user_file_contents 19 | 20 | expect(result[1].definition) 21 | .to match_sexp admin_file_contents 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/finders/model_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::Finders::Model do 2 | before do 3 | ToFactory.models = path 4 | end 5 | 6 | let(:finder) { ToFactory::Finders::Model.new } 7 | let(:path) { "./spec/support/models" } 8 | 9 | describe "#call" do 10 | let!(:user) { ToFactory::User.create! name: "a user" } 11 | let!(:project) { ToFactory::Project.create! name: "a project" } 12 | 13 | context "with a match" do 14 | it do 15 | expect(finder.call).to match_array [user, project] 16 | end 17 | end 18 | 19 | context "no match"do 20 | let(:path) { "./tmp/doesnt_exist" } 21 | it do 22 | expect(finder.call).to eq [] 23 | end 24 | end 25 | 26 | context "with an invalid model file" do 27 | let(:path) { "./spec/support/broken_models" } 28 | before do 29 | allow(finder).to receive(:warn) 30 | end 31 | 32 | it "displays a warning" do 33 | expect(finder.call).to eq [project] 34 | 35 | expect(finder).to have_received(:warn) 36 | .with("Failed to eval ./spec/support/broken_models/invalid_ruby_file.rb") 37 | end 38 | end 39 | 40 | context "with an invalid class" do 41 | before do 42 | allow(finder).to receive(:warn) 43 | end 44 | 45 | it "displays a warning" do 46 | klass = double("BrokenClass", inspect: "BrokenClass") 47 | expect(klass).to receive(:ancestors).and_raise("Some error") 48 | finder.call(klasses: [klass]) 49 | 50 | expect(finder).to have_received(:warn) 51 | .with("Failed to get record from BrokenClass \"Some error\"") 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/generation/attribute_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::Generation::Attribute do 2 | let(:attribute) { ToFactory::Generation::Attribute.new(:some_attributes, a: 1) } 3 | 4 | describe "#to_s" do 5 | it do 6 | expect(attribute.to_s).to include "some_attributes { {:a => 1} }" 7 | end 8 | end 9 | 10 | describe "#format" do 11 | it "formats Date, Time, DateTime" do 12 | Time.zone = "UTC" 13 | time_string = "2011-12-13T14:15 UTC" 14 | 15 | expect(attribute.format(Time .parse(time_string))).to eq "2011-12-13T14:15 UTC".inspect 16 | expect(attribute.format(Date .parse(time_string))).to eq "2011-12-13".inspect 17 | expect(attribute.format(DateTime.parse(time_string))).to eq "2011-12-13T14:15 +00:00".inspect 18 | end 19 | 20 | it "formats Integer, Float"do 21 | expect(attribute.format(123)) .to eq "123" 22 | expect(attribute.format(123.0)).to eq "123.0" 23 | end 24 | 25 | it "formats BigDecimal"do 26 | expect(attribute.format(BigDecimal "123456789012345678900.0")).to eq "BigDecimal.new(\"123456789012345678900.0\")" 27 | end 28 | 29 | it "handles unparseable strings" do 30 | # NB this spec may only have been relevant for ruby 1.8 i.e. older RubyParser versions 31 | # see https://github.com/markburns/to_factory/issues/4 32 | parser = double "RubyParser" 33 | expect(parser).to receive(:parse).and_raise "some error" 34 | attribute.parser = parser 35 | 36 | expect(attribute.format("anything")).to eq "ToFactory: RubyParser exception parsing this attribute".inspect 37 | end 38 | end 39 | 40 | describe "#inspect_value" do 41 | it do 42 | expect(attribute.inspect_value(a: 1)).to eq " { {:a => 1} }" 43 | end 44 | it "formats hashes correctly" do 45 | hash = ActiveSupport::OrderedHash.new 46 | hash[{ "with" => :hash }] = "keys" 47 | hash[2] = "integers" 48 | hash[:a] = { nested: "hash" } 49 | 50 | expected = ' { {{"with" => :hash} => "keys", 2 => "integers", :a => {:nested => "hash"}} }' 51 | 52 | expect(attribute.inspect_value(hash)).to eq expected 53 | end 54 | 55 | it "handles arrays correctly" do 56 | expected = "[1, 2, :a, \"4\"]" 57 | 58 | input = [1, 2, :a, "4"] 59 | expect(attribute.format(input, false)).to eq expected 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/unit/generation/factory_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::Generation::Factory do 2 | before(:each) do 3 | ToFactory::User.destroy_all 4 | ActiveRecord::Base.connection.execute "delete from sqlite_sequence where name = 'users'" 5 | end 6 | 7 | let(:birthday) do 8 | Time.find_zone("UTC").parse("2014-07-08T15:30 UTC") 9 | end 10 | 11 | let!(:user) { create_user! } 12 | 13 | let(:representation) { ToFactory::Representation.from(user) } 14 | let(:generator) { ToFactory::Generation::Factory.new representation } 15 | 16 | describe "#name" do 17 | context "with a simple name" do 18 | let(:representation) { double "Representation", name: "some_name" } 19 | 20 | it "adds just displays the name" do 21 | expect(generator.name).to eq "some_name" 22 | end 23 | end 24 | 25 | context "with a namespace" do 26 | let(:representation) { double "Representation", name: "some_namespace/some_name" } 27 | 28 | it "adds quotes to the name" do 29 | expect(generator.name).to eq "some_namespace/some_name".inspect 30 | end 31 | end 32 | 33 | context "with a an already quoted name in a namespace" do 34 | let(:representation) { double "Representation", name: "some_namespace/some_name".inspect } 35 | 36 | it "doesn't add extra adds quotes to the name" do 37 | expect(generator.name).to eq "some_namespace/some_name".inspect 38 | end 39 | end 40 | end 41 | 42 | describe "#header" do 43 | it do 44 | expect(generator.header {}).to match_sexp <<-eof.strip_heredoc 45 | factory(:"to_factory/user") do 46 | end 47 | eof 48 | end 49 | end 50 | 51 | describe "#attributes" do 52 | let(:representation) do 53 | double(attributes: { "something" => "something", 54 | :id => 123, 55 | :created_at => anything, 56 | :created_on => anything, 57 | :updated_at => anything, 58 | :updated_on => anything, 59 | nil => nil }) 60 | end 61 | it "ignores blank keys, :id, :created_at, :updated_at, :created_on, :updated_on" do 62 | expect(generator.attributes).to eq("something" => "something") 63 | end 64 | end 65 | describe "#factory_attribute" do 66 | it do 67 | expect(generator.factory_attribute(:name, nil)) .to eq " name { nil }" 68 | expect(generator.factory_attribute(:name, "Jeff")).to eq ' name { "Jeff" }' 69 | expect(generator.factory_attribute(:id, 8)) .to eq " id { 8 }" 70 | end 71 | it "generates usable datetime strings" do 72 | output = generator.factory_attribute(:birthday, birthday) 73 | expect(output).to eq ' birthday { "2014-07-08T15:30 UTC" }' 74 | end 75 | end 76 | 77 | describe "#ToFactory" do 78 | let(:expected) do 79 | File.read "./spec/example_factories/user.rb" 80 | end 81 | 82 | it do 83 | expect(generator.to_factory).to match_sexp expected 84 | result = ToFactory(user).values.first.first 85 | expect(result.definition).to match_sexp expected 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/unit/parsing/file_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::Parsing::File do 2 | let(:user_contents) { File.read "spec/example_factories/user.rb" } 3 | let(:admin_contents) { File.read "spec/example_factories/admin.rb" } 4 | 5 | let(:parser) { ToFactory::Parsing::File.from_file(filename) } 6 | 7 | context "with non-parseable factories" do 8 | let(:dynamic_content) do 9 | <<-RUBY 10 | [ 0, 1, 2, 3, 4].each do |i| 11 | Factory.define(:"user_\#{i}", :parent => :"to_factory/user") do |o| 12 | p.project_statuses = statuses 13 | end 14 | end 15 | RUBY 16 | end 17 | let(:instance) { ToFactory::Parsing::File.new(dynamic_content) } 18 | 19 | it do 20 | expect(Kernel).to receive(:warn).with(/#{dynamic_content}/).at_least :once 21 | expect { instance.parse }.not_to raise_error 22 | result = instance.parse 23 | expect(result).to be_a Array 24 | expect(result[0]).to be_a ToFactory::NullRepresentation 25 | expect(result[0].definition).to match_sexp dynamic_content 26 | end 27 | end 28 | 29 | describe "#multiple_factories? and #header?" do 30 | context "new syntax" do 31 | tests = # file, multiple, header 32 | [ 33 | ["user", false, false], 34 | ["user_with_header", false, true], 35 | ["user_admin", true, false], 36 | ["user_admin_with_header", true, true] 37 | ] 38 | 39 | tests.each do |file, multiple, header| 40 | context "with #{header ? 'header' : 'no header'} and #{multiple ? 'multiple factories' : 'one factory'}" do 41 | context "file: #{file}" do 42 | let(:filename) { "spec/example_factories/#{file}.rb" } 43 | 44 | it { expect(parser.multiple_factories?).to eq multiple } 45 | it { expect(parser.header?).to eq header } 46 | end 47 | end 48 | end 49 | end 50 | end 51 | 52 | describe "#parse" do 53 | context "with multiple levels of parent classes" do 54 | let(:filename) { "spec/example_factories/#{'user_admin_super_admin'}.rb" } 55 | 56 | it do 57 | result = parser.parse 58 | 59 | expect(result.map(&:name)).to match_array ["super_admin", "admin", "to_factory/user"] 60 | end 61 | end 62 | 63 | context "file: user" do 64 | let(:filename) { "spec/example_factories/#{'user'}.rb" } 65 | 66 | it do 67 | result = parser.parse 68 | 69 | expect(result.first.definition).to match_sexp user_contents 70 | end 71 | end 72 | 73 | context "file: user_with_header" do 74 | let(:filename) { "spec/example_factories/#{'user_with_header'}.rb" } 75 | 76 | it do 77 | result = parser.parse 78 | 79 | expect(result.first.definition).to match_sexp user_contents 80 | end 81 | end 82 | 83 | context "file: user_admin" do 84 | let(:filename) { "spec/example_factories/#{'user_admin'}.rb" } 85 | 86 | it do 87 | result = parser.parse 88 | 89 | user, admin = result 90 | 91 | expect(user.definition).to match_sexp user_contents 92 | expect(admin.definition).to match_sexp admin_contents 93 | end 94 | end 95 | 96 | context "file: user_admin_with_header" do 97 | let(:filename) { "spec/example_factories/#{'user_admin_with_header'}.rb" } 98 | 99 | it do 100 | result = parser.parse 101 | 102 | expect(result.first.definition).to match_sexp user_contents 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/unit/parsing/klass_inference_spec.rb: -------------------------------------------------------------------------------- 1 | describe ToFactory::KlassInference do 2 | let(:inference) { ToFactory::KlassInference.new representations } 3 | 4 | mappings = [ 5 | [:super_admin, :admin, ToFactory::User, 3], 6 | [:"to_factory/user", nil, ToFactory::User, 1], 7 | [:admin, :"to_factory/user", ToFactory::User, 2], 8 | [:some_project, :"to_factory/project", ToFactory::Project, 2], 9 | [:sub_project, :some_project, ToFactory::Project, 3] 10 | ] 11 | 12 | let(:representations) { mappings.map { |name, parent, _| ToFactory::Representation.new(name, parent) } } 13 | 14 | mappings.each do |name, parent_name, expected_klass, expected_order| 15 | it "having #{name} and #{parent_name.inspect} implies #{expected_klass} #{expected_order} "do 16 | result_klass, order = inference.infer(name.to_s) 17 | 18 | expect(result_klass).to eq expected_klass 19 | expect(order).to eq expected_order 20 | end 21 | end 22 | 23 | it do 24 | expect { inference.infer("unknown") }.to raise_error ToFactory::CannotInferClass 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markburns/to_factory/1acfea5929db744a4fbea003ff227e6e3d02127b/tmp/.keep -------------------------------------------------------------------------------- /to_factory.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "to_factory/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "to_factory" 8 | spec.version = ToFactory::VERSION 9 | spec.authors = ["Mark Burns"] 10 | spec.email = ["markthedeveloper@gmail.com"] 11 | spec.summary = "Turn ActiveRecord instances into factories" 12 | spec.description = "Autogenerate and append/create factory_bot definitions from the console" 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }.reject { |f| f == "spec" || f == "ci" } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "factory_bot" 22 | spec.add_dependency "ruby2ruby" 23 | 24 | spec.add_development_dependency "activerecord" 25 | spec.add_development_dependency "sqlite3" 26 | spec.add_development_dependency "database_cleaner" 27 | 28 | spec.add_development_dependency "byebug" 29 | spec.add_development_dependency "pry" 30 | 31 | spec.add_development_dependency 'simplecov' 32 | spec.add_development_dependency 'rspec-github' 33 | 34 | spec.add_development_dependency "rspec", "~> 3.0" 35 | spec.add_development_dependency "rake", ">= 12.3.3" 36 | end 37 | --------------------------------------------------------------------------------