Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Support for Model Generation #207

Merged
merged 5 commits into from
May 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 22 additions & 33 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lib/napa/cli/generate.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,6 +14,13 @@ class Generate < Thor
'Create a Database Migration'
)

register(
Model,
'model',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

'model <NAME> [field[:type][:index] field[:type][:index]] [options]',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

'Create a Model, including its database migration and test scaffolding'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line detected at class body end.

end
end
end
170 changes: 170 additions & 0 deletions lib/napa/cli/generated_attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
module Napa
module CLI
# 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)
UNIQ_INDEX_OPTIONS = %w(uniq)

attr_accessor :name, :type
attr_reader :attr_options
attr_writer :index_name

class << self
def parse(column_definition)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment Branch Condition size for parse is too high. [15.26/15]

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [95/80]

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [159/80]

# 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent when as deep as case.
Unnecessary spacing detected.

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"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [81/80]
Indent when as deep as case.
Unnecessary spacing detected.

when :text then "MyText"
when :boolean then false
when :references, :belongs_to then nil
else
""
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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end at 126, 8 is not aligned with if at 122, 24

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 required?
self.attr_options[:required]
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
Loading