From 8cf6be56b3d58e6f4b873f3a75bba845e4fc0022 Mon Sep 17 00:00:00 2001 From: Harrison Strowd Date: Mon, 11 May 2015 12:41:23 -0500 Subject: [PATCH 1/5] Separates the GeneratedAttribute class out into a separate file. This will be shared across generators. --- lib/napa/cli/generated_attribute.rb | 150 ++++++++++++++++++++++++++++ lib/napa/cli/migration.rb | 148 +-------------------------- 2 files changed, 151 insertions(+), 147 deletions(-) create mode 100644 lib/napa/cli/generated_attribute.rb diff --git a/lib/napa/cli/generated_attribute.rb b/lib/napa/cli/generated_attribute.rb new file mode 100644 index 0000000..936d38b --- /dev/null +++ b/lib/napa/cli/generated_attribute.rb @@ -0,0 +1,150 @@ +module Napa + module CLI + # directly ported from + # https://github.com/rails/rails/blob/76883f92374c6395f13c16628e1d87d40b6d2399/railties/lib/rails/generators/generated_attribute.rb + class GeneratedAttribute # :nodoc: + INDEX_OPTIONS = %w(index uniq) + UNIQ_INDEX_OPTIONS = %w(uniq) + + attr_accessor :name, :type + attr_reader :attr_options + attr_writer :index_name + + class << self + def parse(column_definition) + name, type, has_index = column_definition.split(':') + + # if user provided "name:index" instead of "name:string:index" + # type should be set blank so GeneratedAttribute's constructor + # could set it to :string + has_index, type = type, nil if INDEX_OPTIONS.include?(type) + + type, attr_options = *parse_type_and_options(type) + type = type.to_sym if type + + if type && reference?(type) + references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { unique: true } : true + attr_options[:index] = references_index + end + + new(name, type, has_index, attr_options) + end + + def reference?(type) + [:references, :belongs_to].include? type + end + + private + + # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to + # when declaring options curly brackets should be used + def parse_type_and_options(type) + case type + when /(string|text|binary|integer)\{(\d+)\}/ + return $1, limit: $2.to_i + when /decimal\{(\d+)[,.-](\d+)\}/ + return :decimal, precision: $1.to_i, scale: $2.to_i + when /(references|belongs_to)\{polymorphic\}/ + return $1, polymorphic: true + else + return type, {} + end + end + end + + def initialize(name, type=nil, index_type=false, attr_options={}) + @name = name + @type = type || :string + @has_index = INDEX_OPTIONS.include?(index_type) + @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type) + @attr_options = attr_options + end + + def field_type + @field_type ||= case type + when :integer then :number_field + when :float, :decimal then :text_field + when :time then :time_select + when :datetime, :timestamp then :datetime_select + when :date then :date_select + when :text then :text_area + when :boolean then :check_box + else + :text_field + end + end + + def default + @default ||= case type + when :integer then 1 + when :float then 1.5 + when :decimal then "9.99" + when :datetime, :timestamp, :time then Time.now.to_s(:db) + when :date then Date.today.to_s(:db) + when :string then name == "type" ? "" : "MyString" + when :text then "MyText" + when :boolean then false + when :references, :belongs_to then nil + else + "" + end + end + + def plural_name + name.sub(/_id$/, '').pluralize + end + + def singular_name + name.sub(/_id$/, '').singularize + end + + def human_name + name.humanize + end + + def index_name + @index_name ||= if polymorphic? + %w(id type).map { |t| "#{name}_#{t}" } + else + column_name + end + end + + def column_name + @column_name ||= reference? ? "#{name}_id" : name + end + + def foreign_key? + !!(name =~ /_id$/) + end + + def reference? + self.class.reference?(type) + end + + def polymorphic? + self.attr_options.has_key?(:polymorphic) + end + + def has_index? + @has_index + end + + def has_uniq_index? + @has_uniq_index + end + + def password_digest? + name == 'password' && type == :digest + end + + def inject_options + "".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } } + end + + def inject_index_options + has_uniq_index? ? ", unique: true" : "" + end + end + end +end diff --git a/lib/napa/cli/migration.rb b/lib/napa/cli/migration.rb index dcc84a0..cdcd360 100644 --- a/lib/napa/cli/migration.rb +++ b/lib/napa/cli/migration.rb @@ -1,5 +1,6 @@ require 'thor' require 'active_support/all' +require 'napa/cli/generated_attribute' module Napa module CLI @@ -76,152 +77,5 @@ def index_name_for(attribute) end.to_sym end end - - # directly ported from - # https://github.com/rails/rails/blob/76883f92374c6395f13c16628e1d87d40b6d2399/railties/lib/rails/generators/generated_attribute.rb - class GeneratedAttribute # :nodoc: - INDEX_OPTIONS = %w(index uniq) - UNIQ_INDEX_OPTIONS = %w(uniq) - - attr_accessor :name, :type - attr_reader :attr_options - attr_writer :index_name - - class << self - def parse(column_definition) - name, type, has_index = column_definition.split(':') - - # if user provided "name:index" instead of "name:string:index" - # type should be set blank so GeneratedAttribute's constructor - # could set it to :string - has_index, type = type, nil if INDEX_OPTIONS.include?(type) - - type, attr_options = *parse_type_and_options(type) - type = type.to_sym if type - - if type && reference?(type) - references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { unique: true } : true - attr_options[:index] = references_index - end - - new(name, type, has_index, attr_options) - end - - def reference?(type) - [:references, :belongs_to].include? type - end - - private - - # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to - # when declaring options curly brackets should be used - def parse_type_and_options(type) - case type - when /(string|text|binary|integer)\{(\d+)\}/ - return $1, limit: $2.to_i - when /decimal\{(\d+)[,.-](\d+)\}/ - return :decimal, precision: $1.to_i, scale: $2.to_i - when /(references|belongs_to)\{polymorphic\}/ - return $1, polymorphic: true - else - return type, {} - end - end - end - - def initialize(name, type=nil, index_type=false, attr_options={}) - @name = name - @type = type || :string - @has_index = INDEX_OPTIONS.include?(index_type) - @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type) - @attr_options = attr_options - end - - def field_type - @field_type ||= case type - when :integer then :number_field - when :float, :decimal then :text_field - when :time then :time_select - when :datetime, :timestamp then :datetime_select - when :date then :date_select - when :text then :text_area - when :boolean then :check_box - else - :text_field - end - end - - def default - @default ||= case type - when :integer then 1 - when :float then 1.5 - when :decimal then "9.99" - when :datetime, :timestamp, :time then Time.now.to_s(:db) - when :date then Date.today.to_s(:db) - when :string then name == "type" ? "" : "MyString" - when :text then "MyText" - when :boolean then false - when :references, :belongs_to then nil - else - "" - end - end - - def plural_name - name.sub(/_id$/, '').pluralize - end - - def singular_name - name.sub(/_id$/, '').singularize - end - - def human_name - name.humanize - end - - def index_name - @index_name ||= if polymorphic? - %w(id type).map { |t| "#{name}_#{t}" } - else - column_name - end - end - - def column_name - @column_name ||= reference? ? "#{name}_id" : name - end - - def foreign_key? - !!(name =~ /_id$/) - end - - def reference? - self.class.reference?(type) - end - - def polymorphic? - self.attr_options.has_key?(:polymorphic) - end - - def has_index? - @has_index - end - - def has_uniq_index? - @has_uniq_index - end - - def password_digest? - name == 'password' && type == :digest - end - - def inject_options - "".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } } - end - - def inject_index_options - has_uniq_index? ? ", unique: true" : "" - end - end end end From 671dde62504d2749dfb191e054dae0760ad362fd Mon Sep 17 00:00:00 2001 From: Harrison Strowd Date: Mon, 11 May 2015 19:20:57 -0500 Subject: [PATCH 2/5] Adds a model generator that creates the ActiveRecord model, the migration, a factory stub, and a rspec test stub. --- lib/napa/cli/generate.rb | 8 + lib/napa/cli/generated_attribute.rb | 22 +- lib/napa/cli/migration.rb | 8 +- lib/napa/cli/model.rb | 130 ++++++++ ...ration_filename%.rb.tt => migration.rb.tt} | 0 .../templates/model/app/models/model.rb.tt | 9 + .../db/migrate/migration.rb.tt} | 0 .../model/spec/factories/factory.rb.tt | 14 + .../model/spec/models/model_spec.rb.tt | 14 + spec/cli/model_spec.rb | 297 ++++++++++++++++++ 10 files changed, 497 insertions(+), 5 deletions(-) create mode 100644 lib/napa/cli/model.rb rename lib/napa/cli/templates/migration/{%migration_filename%.rb.tt => migration.rb.tt} (100%) create mode 100644 lib/napa/cli/templates/model/app/models/model.rb.tt rename lib/napa/cli/templates/{create_table_migration/%migration_filename%.rb.tt => model/db/migrate/migration.rb.tt} (100%) create mode 100644 lib/napa/cli/templates/model/spec/factories/factory.rb.tt create mode 100644 lib/napa/cli/templates/model/spec/models/model_spec.rb.tt create mode 100644 spec/cli/model_spec.rb diff --git a/lib/napa/cli/generate.rb b/lib/napa/cli/generate.rb index 91ec759..2832039 100644 --- a/lib/napa/cli/generate.rb +++ b/lib/napa/cli/generate.rb @@ -1,6 +1,7 @@ require "napa/cli/generate/api" require "napa/cli/generate/readme" require "napa/cli/migration" +require "napa/cli/model" module Napa module CLI @@ -13,6 +14,13 @@ class Generate < Thor 'Create a Database Migration' ) + register( + Model, + 'model', + 'model [field[:type][:index] field[:type][:index]] [options]', + 'Create a Model, including it\'s database migration and test scaffolding' + ) + end end end diff --git a/lib/napa/cli/generated_attribute.rb b/lib/napa/cli/generated_attribute.rb index 936d38b..4475753 100644 --- a/lib/napa/cli/generated_attribute.rb +++ b/lib/napa/cli/generated_attribute.rb @@ -1,6 +1,6 @@ module Napa module CLI - # directly ported from + # directly ported with slight modification from # https://github.com/rails/rails/blob/76883f92374c6395f13c16628e1d87d40b6d2399/railties/lib/rails/generators/generated_attribute.rb class GeneratedAttribute # :nodoc: INDEX_OPTIONS = %w(index uniq) @@ -90,6 +90,22 @@ def default end end + def factory_stub + case type + when :integer then "1" + when :float then "1.5" + when :decimal then "9.99" + when :datetime, :timestamp, :time then "{ Time.now }" + when :date then "{ Date.today }" + when :string then '"MyString"' + when :text then '"MyText"' + when :boolean then "false" + when :digest then '"password"' + else + '"Unknown"' + end + end + def plural_name name.sub(/_id$/, '').pluralize end @@ -126,6 +142,10 @@ def polymorphic? self.attr_options.has_key?(:polymorphic) end + def required? + self.attr_options[:required] + end + def has_index? @has_index end diff --git a/lib/napa/cli/migration.rb b/lib/napa/cli/migration.rb index cdcd360..68939f6 100644 --- a/lib/napa/cli/migration.rb +++ b/lib/napa/cli/migration.rb @@ -32,7 +32,7 @@ def parse_attributes! end def set_local_assigns! - @migration_template = "migration" + @migration_template = "migration/migration.rb.tt" filename = migration_name.underscore case filename when /^(add|remove)_.*_(?:to|from)_(.*)/ @@ -47,14 +47,14 @@ def set_local_assigns! end when /^create_(.+)/ @table_name = $1.pluralize - @migration_template = "create_table_migration" + @migration_template = "model/db/migrate/migration.rb.tt" end end def migration - self.class.source_root "#{File.dirname(__FILE__)}/templates/#{@migration_template}" + self.class.source_root "#{File.dirname(__FILE__)}/templates/" say 'Generating migration...' - directory '.', output_directory + template @migration_template, "#{output_directory}/#{migration_filename}.rb" say 'Done!', :green end diff --git a/lib/napa/cli/model.rb b/lib/napa/cli/model.rb new file mode 100644 index 0000000..e722fb6 --- /dev/null +++ b/lib/napa/cli/model.rb @@ -0,0 +1,130 @@ +require 'thor' +require 'active_support/all' +require 'napa/cli/generated_attribute' + +module Napa + module CLI + class Error < Thor::Error # :nodoc: + end + + # Note: This currently does not support namespaced model creation. + class Model < Thor::Group + include Thor::Actions + + # Largely modeled off of Rails's: + # - activerecord/lib/rails/generators/active_record/model/model_generator.rb, + # - railties/lib/rails/generators/base.rb, and + # - railties/lib/rails/generators/named_base.rb + + argument :name + argument :attributes, :type => :array, :default => [] + + class_option :migration, :type => :boolean, :default => true + class_option :timestamps, :type => :boolean, :default => true + class_option :indexes, :type => :boolean, :default => true + class_option :parent, :type => :string + + def parse_attributes! + self.attributes = (attributes || []).map do |attr| + GeneratedAttribute.parse(attr) + end + end + + def generate_model + # In order to check for class collisions, the Grape environment needs to be loaded. + say 'Generating model...' + self.class.source_root "#{File.dirname(__FILE__)}/templates" + create_migration_file + create_model_file + create_factory_file + create_model_spec_file + say 'Done!', :green + end + + protected + + def version + Time.now.utc.strftime("%Y%m%d%H%M%S") + end + + def create_migration_file + return unless options[:migration] && options[:parent].nil? + attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false + + # This does not currently check for similar migrations. + # This can be done by overriding the CreateFile Thor action and + # implementing the exists? and on_conflict_behavior methods. + template migration_template, "#{migration_output_directory}/#{version}_#{migration_name}.rb" + end + + def create_model_file + template model_template, "#{model_output_directory}/#{model_name}.rb" + end + + def create_factory_file + template factory_template, "#{factory_output_directory}/#{table_name}.rb" + end + + def create_model_spec_file + template model_spec_template, "#{model_spec_output_directory}/#{model_name}_spec.rb" + end + + def model_name + name.underscore + end + + def migration_name + "create_#{table_name}" + end + + def table_name + model_name.pluralize + end + + def migration_template + "model/db/migrate/migration.rb.tt" + end + + def parent_class_name + options[:parent] || "ActiveRecord::Base" + end + + def migration_output_directory + './db/migrate' + end + + def model_template + "model/app/models/model.rb.tt" + end + + def model_output_directory + './app/models' + end + + def factory_template + "model/spec/factories/factory.rb.tt" + end + + def factory_output_directory + './spec/factories' + end + + def model_spec_template + "model/spec/models/model_spec.rb.tt" + end + + def model_spec_output_directory + './spec/models' + end + + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + + def accessible_attributes + attributes.reject(&:reference?) + end + + end + end +end diff --git a/lib/napa/cli/templates/migration/%migration_filename%.rb.tt b/lib/napa/cli/templates/migration/migration.rb.tt similarity index 100% rename from lib/napa/cli/templates/migration/%migration_filename%.rb.tt rename to lib/napa/cli/templates/migration/migration.rb.tt diff --git a/lib/napa/cli/templates/model/app/models/model.rb.tt b/lib/napa/cli/templates/model/app/models/model.rb.tt new file mode 100644 index 0000000..8a2ec36 --- /dev/null +++ b/lib/napa/cli/templates/model/app/models/model.rb.tt @@ -0,0 +1,9 @@ +class <%= model_name.camelize %> < <%= parent_class_name.classify %> +<% attributes.select(&:reference?).each do |attribute| -%> + belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> +<% end -%> +<% if attributes.any?(&:password_digest?) -%> + # Note: This requires bcrypt to be added to your Gemfile. + has_secure_password +<% end -%> +end diff --git a/lib/napa/cli/templates/create_table_migration/%migration_filename%.rb.tt b/lib/napa/cli/templates/model/db/migrate/migration.rb.tt similarity index 100% rename from lib/napa/cli/templates/create_table_migration/%migration_filename%.rb.tt rename to lib/napa/cli/templates/model/db/migrate/migration.rb.tt diff --git a/lib/napa/cli/templates/model/spec/factories/factory.rb.tt b/lib/napa/cli/templates/model/spec/factories/factory.rb.tt new file mode 100644 index 0000000..5265c31 --- /dev/null +++ b/lib/napa/cli/templates/model/spec/factories/factory.rb.tt @@ -0,0 +1,14 @@ +FactoryGirl.define do + factory :<%= model_name %> do +<% attributes.each do |attribute| -%> +<% if attribute.reference? -%> + association :<%= attribute.name %> +<% elsif attribute.password_digest? -%> + password <%= attribute.factory_stub %> + password_confirmation <%= attribute.factory_stub %> +<% else -%> + <%= attribute.name %> <%= attribute.factory_stub %> +<% end -%> +<% end -%> + end +end diff --git a/lib/napa/cli/templates/model/spec/models/model_spec.rb.tt b/lib/napa/cli/templates/model/spec/models/model_spec.rb.tt new file mode 100644 index 0000000..327567e --- /dev/null +++ b/lib/napa/cli/templates/model/spec/models/model_spec.rb.tt @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe <%= model_name.camelize %> do + + it 'can be created' do + <%= model_name %> = create :<%= model_name %> + expect(<%= model_name %>).to_not be_nil + end + + it 'needs tests to be written!' do + pending('write tests for <%= model_name.camelize.classify %>!') + end + +end diff --git a/spec/cli/model_spec.rb b/spec/cli/model_spec.rb new file mode 100644 index 0000000..79486c8 --- /dev/null +++ b/spec/cli/model_spec.rb @@ -0,0 +1,297 @@ +require 'spec_helper' +require 'napa/cli/model' + +describe Napa::CLI::Model do + let(:test_output_directory) { 'spec/tmp' } + let(:version) { '20000101000000' } + + silence_thor + + before do + allow_any_instance_of(described_class).to receive(:model_output_directory).and_return(test_output_directory) + allow_any_instance_of(described_class).to receive(:migration_output_directory).and_return(test_output_directory) + allow_any_instance_of(described_class).to receive(:factory_output_directory).and_return(test_output_directory) + allow_any_instance_of(described_class).to receive(:model_spec_output_directory).and_return(test_output_directory) + allow_any_instance_of(described_class).to receive(:version).and_return(version) + end + + after do + FileUtils.rm_rf(test_output_directory) + end + + describe 'Car model' do + before do + model = Napa::CLI::Model.new([ 'Car', + 'transmission_type:references', + 'has_navigation:boolean' ], + [ '--parent=Vehicle' ]) + model.invoke_all + + # The migration should not be created because a parent model is specified. + @migration_file = File.join(test_output_directory, "#{version}_create_cars.rb") + + expected_model_file = File.join(test_output_directory, 'car.rb') + @model_content = File.read(expected_model_file) + + expected_model_spec_file = File.join(test_output_directory, 'car_spec.rb') + @model_spec_content = File.read(expected_model_spec_file) + + expected_factory_file = File.join(test_output_directory, 'cars.rb') + @factory_content = File.read(expected_factory_file) + end + + describe 'migration' do + it 'is not created' do + expect(File.exists?(@migration_file)).to eq(false) + end + end + + describe 'model' do + it 'creates a model class' do + expect(@model_content).to match(/class Car/) + end + + it 'model inherits from ActiveRecord::Base' do + expect(@model_content).to match(/Car < Vehicle/) + end + + it 'adds belongs_to association for references attributes' do + expect(@model_content).to match(/belongs_to :transmission_type/) + end + end + + describe 'model spec' do + it 'loads the spec_helper' do + expect(@model_spec_content).to match(/require 'spec_helper'/) + end + + it 'describes the model' do + expect(@model_spec_content).to match(/describe Car/) + end + + it 'tests the creation of an model instance' do + expect(@model_spec_content).to match(/car = create :car/) + end + + it 'creates a pending test' do + expect(@model_spec_content).to match(/pending\('.*'\)/) + end + end + + describe 'factory' do + it 'creates a factory for the model' do + expect(@factory_content).to match(/factory :car/) + end + + it 'creates a stub for each attribute' do + expect(@factory_content).to match(/association :transmission_type/) + expect(@factory_content).to match(/has_navigation false/) + end + end + + after do + FileUtils.rm_rf(test_output_directory) + end + end + + describe 'User model' do + before do + Napa::CLI::Model.new([ 'User', + 'username:string:index', + 'password:digest', + 'referrer:references', + 'birth_date:date:index' ]).invoke_all + + expected_migration_file = File.join(test_output_directory, "#{version}_create_users.rb") + @migration_content = File.read(expected_migration_file) + + expected_model_file = File.join(test_output_directory, 'user.rb') + @model_content = File.read(expected_model_file) + + expected_model_spec_file = File.join(test_output_directory, 'user_spec.rb') + @model_spec_content = File.read(expected_model_spec_file) + + expected_factory_file = File.join(test_output_directory, 'users.rb') + @factory_content = File.read(expected_factory_file) + end + + describe 'migration' do + it 'creates a creates a camelized migration class' do + expect(@migration_content).to match(/class CreateUsers/) + end + + it 'creates a table for the new model' do + expect(@migration_content).to match(/create_table :users/) + end + + it 'adds the specified columns' do + expect(@migration_content).to match(/t.string :username/) + end + + it 'adds the specified associations' do + expect(@migration_content).to match(/t.references :referrer/) + end + + it 'adds password digest column' do + expect(@migration_content).to match(/t.string :password_digest/) + end + + it 'adds the necessary indexes' do + expect(@migration_content).to match(/add_index :users, :username/) + expect(@migration_content).to match(/:referrer, index: true/) + expect(@migration_content).to match(/add_index :users, :birth_date/) + end + end + + describe 'model' do + it 'creates a model class' do + expect(@model_content).to match(/class User/) + end + + it 'model inherits from ActiveRecord::Base' do + expect(@model_content).to match(/User < ActiveRecord::Base/) + end + + it 'adds has_secure_password for password digest attributes' do + expect(@model_content).to match(/has_secure_password/) + end + end + + describe 'model spec' do + it 'loads the spec_helper' do + expect(@model_spec_content).to match(/require 'spec_helper'/) + end + + it 'describes the model' do + expect(@model_spec_content).to match(/describe User/) + end + + it 'tests the creation of an model instance' do + expect(@model_spec_content).to match(/user = create :user/) + end + + it 'creates a pending test' do + expect(@model_spec_content).to match(/pending\('.*'\)/) + end + end + + describe 'factory' do + it 'creates a factory for the model' do + expect(@factory_content).to match(/factory :user/) + end + + it 'creates a stub for each attribute' do + expect(@factory_content).to match(/username "MyString"/) + expect(@factory_content).to match(/password "password"/) + expect(@factory_content).to match(/association :referrer/) + expect(@factory_content).to match(/birth_date { Date.today }/) + end + + it 'creates a confirmation for password attributes' do + expect(@factory_content).to match(/password_confirmation "password"/) + end + end + + after do + FileUtils.rm_rf(test_output_directory) + end + end + + describe 'Gender model' do + before do + model = Napa::CLI::Model.new([ 'Gender', + 'name:string', + 'personal_characteristic:belongs_to{polymorphic}' ], + [ '--no-timestamps' ]) + model.invoke_all + + expected_migration_file = File.join(test_output_directory, "#{version}_create_genders.rb") + @migration_content = File.read(expected_migration_file) + + expected_model_file = File.join(test_output_directory, 'gender.rb') + @model_content = File.read(expected_model_file) + + expected_model_spec_file = File.join(test_output_directory, 'gender_spec.rb') + @model_spec_content = File.read(expected_model_spec_file) + + expected_factory_file = File.join(test_output_directory, 'genders.rb') + @factory_content = File.read(expected_factory_file) + end + + describe 'migration' do + it 'creates a migration class' do + expect(@migration_content).to match(/class CreateGenders/) + end + + it 'creates a table for the new model' do + expect(@migration_content).to match(/create_table :genders/) + end + + it 'adds the specified columns' do + expect(@migration_content).to match(/t.string :name/) + end + + it 'adds the specified associations' do + expect(@migration_content).to match(/t.belongs_to :personal_characteristic/) + end + + it 'sets up the polymorphic associations' do + expect(@migration_content).to match(/:personal_characteristic, polymorphic: true/) + end + + it 'does NOT add timestamps to the table' do + expect(@migration_content).to_not match(/t.timestamps/) + end + end + + describe 'model' do + it 'creates a model class' do + expect(@model_content).to match(/class Gender/) + end + + it 'model inherits from ActiveRecord::Base' do + expect(@model_content).to match(/Gender < ActiveRecord::Base/) + end + + it 'adds belongs_to associations' do + expect(@model_content).to match(/belongs_to :personal_characteristic/) + end + + it 'sets up polymorphic associations' do + expect(@model_content).to match(/:personal_characteristic, polymorphic: true/) + end + end + + describe 'model spec' do + it 'loads the spec_helper' do + expect(@model_spec_content).to match(/require 'spec_helper'/) + end + + it 'describes the model' do + expect(@model_spec_content).to match(/describe Gender/) + end + + it 'tests the creation of an model instance' do + expect(@model_spec_content).to match(/gender = create :gender/) + end + + it 'creates a pending test' do + expect(@model_spec_content).to match(/pending\('.*'\)/) + end + end + + describe 'factory' do + it 'creates a factory for the model' do + expect(@factory_content).to match(/factory :gender/) + end + + it 'creates a stub for each attribute' do + expect(@factory_content).to match(/name "MyString"/) + end + end + + after do + FileUtils.rm_rf(test_output_directory) + end + end +end From 4e7b3257bf45a46463966f574fc99d9f3a378529 Mon Sep 17 00:00:00 2001 From: Harrison Strowd Date: Mon, 11 May 2015 20:24:54 -0500 Subject: [PATCH 3/5] Removes model creation from API generator. Updates Getting Started Guide. --- docs/quickstart.md | 55 ++++++++----------- .../api/app/models/%name_underscore%.rb.tt | 2 - .../spec/models/%name_underscore%_spec.rb.tt | 9 --- spec/cli/generate/api_spec.rb | 14 ----- 4 files changed, 22 insertions(+), 58 deletions(-) delete mode 100644 lib/napa/cli/templates/api/app/models/%name_underscore%.rb.tt delete mode 100644 lib/napa/cli/templates/api/spec/models/%name_underscore%_spec.rb.tt diff --git a/docs/quickstart.md b/docs/quickstart.md index 80e4359..0dc06c4 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -69,63 +69,52 @@ Once your databases are setup, you can run `rspec` to verify everything is worki rspec spec ``` -## API Generator +## Model Generator -Now that we have our service scaffolded up, let's generate an API. +Now that we have our service scaffolded up, let's generate the data model for our service. -Napa includes an API generator which will create a Grape API, Model and Representer by running: +Napa includes a Model generator that will create an ActiveRecord model, the associated database migration, a factory stub for testing, and an initial rspec test. To invoke this, run: ``` -napa generate api person +napa generate model Person name:string job_title:string email:string ``` -**Note:** the generator will pluralize the name of your resource for the API, so use the singular version in the generator. - You will see the following output: ``` -Generating api... - create app/apis/people_api.rb +Generating model... + create db/migrate/20140411163743_create_people.rb create app/models/person.rb - create app/representers/person_representer.rb + create spec/factories/people.rb + create spec/models/person_spec.rb Done! ``` -## Create a Person model - -From the output above, we can see that the generated create a `Person` model, so we should create a migration to actually build the table for that in our database. So, let's run: +Now we're ready to create and setup our databases by running: ``` -napa generate migration CreatePerson name job_title email +rake db:migrate +RACK_ENV=test rake db:migrate ``` -You will see the following output: +## API Generator + +Next, let's generate an API to expose this information. Napa also includes an API generator which will create a Grape API, Model and Representer by running: ``` -Generating migration... - create db/migrate - create db/migrate/20140411163743_create_person.rb -Done! +napa generate api person ``` -Open up that migration file and see the generated migration for the `people` table: - -```ruby -class CreatePerson < ActiveRecord::Migration - def change - create_table :people do |t| - t.string :name - t.string :job_title - t.string :email - end - end -end -``` +**Note:** the generator will pluralize the name of your resource for the API, so use the singular version in the generator. -Then you can run: +You will see the following output: ``` -rake db:migrate +Generating api... + create app/apis/people_api.rb + create app/representers/person_representer.rb + create spec/apis/people_api_spec.rb +Done! ``` ## Declare these attributes in the API and Representer diff --git a/lib/napa/cli/templates/api/app/models/%name_underscore%.rb.tt b/lib/napa/cli/templates/api/app/models/%name_underscore%.rb.tt deleted file mode 100644 index 6a45d2b..0000000 --- a/lib/napa/cli/templates/api/app/models/%name_underscore%.rb.tt +++ /dev/null @@ -1,2 +0,0 @@ -class <%= name.classify %> < ActiveRecord::Base -end diff --git a/lib/napa/cli/templates/api/spec/models/%name_underscore%_spec.rb.tt b/lib/napa/cli/templates/api/spec/models/%name_underscore%_spec.rb.tt deleted file mode 100644 index 371d4e3..0000000 --- a/lib/napa/cli/templates/api/spec/models/%name_underscore%_spec.rb.tt +++ /dev/null @@ -1,9 +0,0 @@ -require 'spec_helper' - -describe <%= name.classify %> do - - it 'needs tests to be written!' do - pending('write tests for <%= name.classify %>!') - end - -end diff --git a/spec/cli/generate/api_spec.rb b/spec/cli/generate/api_spec.rb index 85ffb4a..adbbeda 100644 --- a/spec/cli/generate/api_spec.rb +++ b/spec/cli/generate/api_spec.rb @@ -24,13 +24,6 @@ expect(api_code).to match(/class FoosApi/) end - it 'creates a model class' do - expected_model_file = File.join(test_api_directory, 'app/models/foo.rb') - model_code = File.read(expected_model_file) - - expect(model_code).to match(/class Foo/) - end - it 'creates a representer class' do expected_representer_file = File.join(test_api_directory, 'app/representers/foo_representer.rb') representer_code = File.read(expected_representer_file) @@ -52,13 +45,6 @@ expect(api_code).to match(/describe FoosApi/) end - - it 'creates a model spec' do - expected_model_file = File.join(test_api_directory, 'spec/models/foo_spec.rb') - model_code = File.read(expected_model_file) - - expect(model_code).to match(/describe Foo/) - end end end From 4ce5b39dbebebb9435382894a81a5d660fb965e6 Mon Sep 17 00:00:00 2001 From: Harrison Strowd Date: Tue, 12 May 2015 21:36:45 -0500 Subject: [PATCH 4/5] Removes unused model generation helper method. --- lib/napa/cli/model.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/napa/cli/model.rb b/lib/napa/cli/model.rb index e722fb6..bc80dbc 100644 --- a/lib/napa/cli/model.rb +++ b/lib/napa/cli/model.rb @@ -121,10 +121,6 @@ def attributes_with_index attributes.select { |a| !a.reference? && a.has_index? } end - def accessible_attributes - attributes.reject(&:reference?) - end - end end end From 2f6efb3af61c0fea582717d9f65499e798111eb5 Mon Sep 17 00:00:00 2001 From: Harrison Strowd Date: Tue, 12 May 2015 21:37:37 -0500 Subject: [PATCH 5/5] Fixes grammar on model generator description. --- lib/napa/cli/generate.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/napa/cli/generate.rb b/lib/napa/cli/generate.rb index 2832039..c42d383 100644 --- a/lib/napa/cli/generate.rb +++ b/lib/napa/cli/generate.rb @@ -18,7 +18,7 @@ class Generate < Thor Model, 'model', 'model [field[:type][:index] field[:type][:index]] [options]', - 'Create a Model, including it\'s database migration and test scaffolding' + 'Create a Model, including its database migration and test scaffolding' ) end