napa 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 411eb368134bee542c4fb51fa4a180c5b1a65ccc
4
- data.tar.gz: d2ca60df98d4c3f260e5c0df9864087bbda48dcd
3
+ metadata.gz: 91335a0b0cd0c3a0efd87528894698508f26f51f
4
+ data.tar.gz: 04c64e2dd763c45a38a89d8a84e0f5bdaf80dbad
5
5
  SHA512:
6
- metadata.gz: c950462ba0a5d7c8e8c249a4d23243e1d91e742ead2aef1ca760af45b2bf8412340e1474f433a5fa1c0280c9ac18bb74cef37ed471ec4c82a54abc49b5eb31d9
7
- data.tar.gz: 56aa083725ab045cfcafd55ff579e0ad617fdadbd90aa5df16eee7db7efc1929b26ff30040cfaf50b708e5603d31d9ef74707bee01ad92bcbacf8b40341fd0d1
6
+ metadata.gz: 1f25478d3900145201937e2b3f79c2f3cc7fd8d4c0ce8c2a0656137773d557eb2f34dd3e976bf5a6f5698c395c6b4a762e917a5388585891a7f24a549e10461d
7
+ data.tar.gz: d257e109aed088d50f701873bdf1d56cfff66f2f9df2fb4f2b2d4b6bcd07f0aecd26c9cd1b906b5c2c15e46575e834f11a8dfbf2f729133e7284cc67508957d9
@@ -0,0 +1,6 @@
1
+ .env
2
+ .env.test
3
+ .git
4
+ .rubocop.yml
5
+ .travis.yml
6
+ log/*
@@ -1,5 +1,10 @@
1
1
  master
2
2
  ===
3
+ * [Support for Rails style Model Generation](https://github.com/bellycard/napa/pull/207)
4
+ * [Resolves issue #205 by adding hashie-forbidden_attributes gem to generated Gemfile](https://github.com/bellycard/napa/pull/206)
5
+ * [use RAILS_ENV if RACK_ENV not found](https://github.com/bellycard/napa/pull/200)
6
+ * [Support for creating and dropping databases on non-standard ports](https://github.com/bellycard/napa/pull/166)
7
+ * Added an rspec response helper `expect_error_code` for easier testing of API methods which are supposed to send back a JSON representation of an error
3
8
 
4
9
  0.4.3
5
10
  ===
@@ -0,0 +1,29 @@
1
+ FROM ruby:2.0.0-p643
2
+
3
+ # Install deps
4
+ RUN apt-get update \
5
+ && apt-get install -y libreadline-dev nano vim \
6
+ && apt-get purge -y --auto-remove
7
+
8
+ # Ensure Gemfile.lock is up to date
9
+ RUN bundle config --global frozen 1
10
+
11
+ # Install latest released Napa to get baseline dependencies
12
+ RUN gem install napa
13
+
14
+ # Add a simple Procfile parser
15
+ ADD contrib/start.rb /start
16
+
17
+ # Create directory for app
18
+ RUN mkdir -p /usr/src/app
19
+ WORKDIR /usr/src/app
20
+
21
+ # Copy Gemfile and Gemfile.lock and run bundle install
22
+ ONBUILD COPY Gemfile /usr/src/app/
23
+ ONBUILD COPY Gemfile.lock /usr/src/app/
24
+ ONBUILD RUN bundle install
25
+
26
+ # Copy rest of app
27
+ ONBUILD COPY . /usr/src/app
28
+
29
+ CMD ["/start", "web"]
@@ -0,0 +1,19 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'yaml'
4
+
5
+ # attempt to parse Procfile
6
+ begin
7
+ proc = YAML.load_file('Procfile')
8
+ command = proc[ARGV[0]]
9
+
10
+ if command
11
+ exec(command)
12
+ else
13
+ puts "Could not find #{ARGV[0]} in Procfile"
14
+ exit 1
15
+ end
16
+ rescue
17
+ puts 'Failed to parse Procfile'
18
+ exit 2
19
+ end
@@ -69,63 +69,52 @@ Once your databases are setup, you can run `rspec` to verify everything is worki
69
69
  rspec spec
70
70
  ```
71
71
 
72
- ## API Generator
72
+ ## Model Generator
73
73
 
74
- Now that we have our service scaffolded up, let's generate an API.
74
+ Now that we have our service scaffolded up, let's generate the data model for our service.
75
75
 
76
- Napa includes an API generator which will create a Grape API, Model and Representer by running:
76
+ 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:
77
77
 
78
78
  ```
79
- napa generate api person
79
+ napa generate model Person name:string job_title:string email:string
80
80
  ```
81
81
 
82
- **Note:** the generator will pluralize the name of your resource for the API, so use the singular version in the generator.
83
-
84
82
  You will see the following output:
85
83
 
86
84
  ```
87
- Generating api...
88
- create app/apis/people_api.rb
85
+ Generating model...
86
+ create db/migrate/20140411163743_create_people.rb
89
87
  create app/models/person.rb
90
- create app/representers/person_representer.rb
88
+ create spec/factories/people.rb
89
+ create spec/models/person_spec.rb
91
90
  Done!
92
91
  ```
93
92
 
94
- ## Create a Person model
95
-
96
- 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:
93
+ Now we're ready to create and setup our databases by running:
97
94
 
98
95
  ```
99
- napa generate migration CreatePerson name job_title email
96
+ rake db:migrate
97
+ RACK_ENV=test rake db:migrate
100
98
  ```
101
99
 
102
- You will see the following output:
100
+ ## API Generator
101
+
102
+ 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:
103
103
 
104
104
  ```
105
- Generating migration...
106
- create db/migrate
107
- create db/migrate/20140411163743_create_person.rb
108
- Done!
105
+ napa generate api person
109
106
  ```
110
107
 
111
- Open up that migration file and see the generated migration for the `people` table:
112
-
113
- ```ruby
114
- class CreatePerson < ActiveRecord::Migration
115
- def change
116
- create_table :people do |t|
117
- t.string :name
118
- t.string :job_title
119
- t.string :email
120
- end
121
- end
122
- end
123
- ```
108
+ **Note:** the generator will pluralize the name of your resource for the API, so use the singular version in the generator.
124
109
 
125
- Then you can run:
110
+ You will see the following output:
126
111
 
127
112
  ```
128
- rake db:migrate
113
+ Generating api...
114
+ create app/apis/people_api.rb
115
+ create app/representers/person_representer.rb
116
+ create spec/apis/people_api_spec.rb
117
+ Done!
129
118
  ```
130
119
 
131
120
  ## Declare these attributes in the API and Representer
@@ -201,17 +190,7 @@ To create a person we will send a `POST` request to our API.
201
190
  curl -X POST -d name="Darby Frey" -d job_title="Software Engineer" -d email="darbyfrey@gmail.com" http://localhost:9393/people
202
191
  ```
203
192
 
204
- **SIDENOTE:** At this point, you will likely get an error response like the one below. By default Napa ships with the `Napa::Middleware::Authentication` enabled. For the purposes of this guide, we will disable this middleware to make the requests easier to construct. Be sure to re-enable this before you go to production. To disable the middleware, just comment out the `use Napa::Middleware::Authentication` line in `config.ru` and restart shotgun. Also, see the Napa::Middleware::Authentication docs for more details.
205
-
206
- ```json
207
- {
208
- "error": {
209
- "code": "not_configured",
210
- "message": "password not configured"
211
- }
212
- }
213
- ```
214
-
193
+ **SIDENOTE:** Napa ships with authentication support via the `Napa::Middleware::Authentication` middleware. This middleware is disabled by default, but can be enabled by uncommenting the `use Napa::Middleware::Authentication` line in `config.ru` and restarting shotgun. Also, see the Napa::Middleware::Authentication docs for more details.
215
194
 
216
195
  All response from Napa include the `data` key. In this case the newly created person object is returned nested within the `data` key.
217
196
 
@@ -1,6 +1,7 @@
1
1
  require "napa/cli/generate/api"
2
2
  require "napa/cli/generate/readme"
3
3
  require "napa/cli/migration"
4
+ require "napa/cli/model"
4
5
 
5
6
  module Napa
6
7
  module CLI
@@ -13,6 +14,13 @@ module Napa
13
14
  'Create a Database Migration'
14
15
  )
15
16
 
17
+ register(
18
+ Model,
19
+ 'model',
20
+ 'model <NAME> [field[:type][:index] field[:type][:index]] [options]',
21
+ 'Create a Model, including its database migration and test scaffolding'
22
+ )
23
+
16
24
  end
17
25
  end
18
26
  end
@@ -0,0 +1,170 @@
1
+ module Napa
2
+ module CLI
3
+ # directly ported with slight modification from
4
+ # https://github.com/rails/rails/blob/76883f92374c6395f13c16628e1d87d40b6d2399/railties/lib/rails/generators/generated_attribute.rb
5
+ class GeneratedAttribute # :nodoc:
6
+ INDEX_OPTIONS = %w(index uniq)
7
+ UNIQ_INDEX_OPTIONS = %w(uniq)
8
+
9
+ attr_accessor :name, :type
10
+ attr_reader :attr_options
11
+ attr_writer :index_name
12
+
13
+ class << self
14
+ def parse(column_definition)
15
+ name, type, has_index = column_definition.split(':')
16
+
17
+ # if user provided "name:index" instead of "name:string:index"
18
+ # type should be set blank so GeneratedAttribute's constructor
19
+ # could set it to :string
20
+ has_index, type = type, nil if INDEX_OPTIONS.include?(type)
21
+
22
+ type, attr_options = *parse_type_and_options(type)
23
+ type = type.to_sym if type
24
+
25
+ if type && reference?(type)
26
+ references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { unique: true } : true
27
+ attr_options[:index] = references_index
28
+ end
29
+
30
+ new(name, type, has_index, attr_options)
31
+ end
32
+
33
+ def reference?(type)
34
+ [:references, :belongs_to].include? type
35
+ end
36
+
37
+ private
38
+
39
+ # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to
40
+ # when declaring options curly brackets should be used
41
+ def parse_type_and_options(type)
42
+ case type
43
+ when /(string|text|binary|integer)\{(\d+)\}/
44
+ return $1, limit: $2.to_i
45
+ when /decimal\{(\d+)[,.-](\d+)\}/
46
+ return :decimal, precision: $1.to_i, scale: $2.to_i
47
+ when /(references|belongs_to)\{polymorphic\}/
48
+ return $1, polymorphic: true
49
+ else
50
+ return type, {}
51
+ end
52
+ end
53
+ end
54
+
55
+ def initialize(name, type=nil, index_type=false, attr_options={})
56
+ @name = name
57
+ @type = type || :string
58
+ @has_index = INDEX_OPTIONS.include?(index_type)
59
+ @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type)
60
+ @attr_options = attr_options
61
+ end
62
+
63
+ def field_type
64
+ @field_type ||= case type
65
+ when :integer then :number_field
66
+ when :float, :decimal then :text_field
67
+ when :time then :time_select
68
+ when :datetime, :timestamp then :datetime_select
69
+ when :date then :date_select
70
+ when :text then :text_area
71
+ when :boolean then :check_box
72
+ else
73
+ :text_field
74
+ end
75
+ end
76
+
77
+ def default
78
+ @default ||= case type
79
+ when :integer then 1
80
+ when :float then 1.5
81
+ when :decimal then "9.99"
82
+ when :datetime, :timestamp, :time then Time.now.to_s(:db)
83
+ when :date then Date.today.to_s(:db)
84
+ when :string then name == "type" ? "" : "MyString"
85
+ when :text then "MyText"
86
+ when :boolean then false
87
+ when :references, :belongs_to then nil
88
+ else
89
+ ""
90
+ end
91
+ end
92
+
93
+ def factory_stub
94
+ case type
95
+ when :integer then "1"
96
+ when :float then "1.5"
97
+ when :decimal then "9.99"
98
+ when :datetime, :timestamp, :time then "{ Time.now }"
99
+ when :date then "{ Date.today }"
100
+ when :string then '"MyString"'
101
+ when :text then '"MyText"'
102
+ when :boolean then "false"
103
+ when :digest then '"password"'
104
+ else
105
+ '"Unknown"'
106
+ end
107
+ end
108
+
109
+ def plural_name
110
+ name.sub(/_id$/, '').pluralize
111
+ end
112
+
113
+ def singular_name
114
+ name.sub(/_id$/, '').singularize
115
+ end
116
+
117
+ def human_name
118
+ name.humanize
119
+ end
120
+
121
+ def index_name
122
+ @index_name ||= if polymorphic?
123
+ %w(id type).map { |t| "#{name}_#{t}" }
124
+ else
125
+ column_name
126
+ end
127
+ end
128
+
129
+ def column_name
130
+ @column_name ||= reference? ? "#{name}_id" : name
131
+ end
132
+
133
+ def foreign_key?
134
+ !!(name =~ /_id$/)
135
+ end
136
+
137
+ def reference?
138
+ self.class.reference?(type)
139
+ end
140
+
141
+ def polymorphic?
142
+ self.attr_options.has_key?(:polymorphic)
143
+ end
144
+
145
+ def required?
146
+ self.attr_options[:required]
147
+ end
148
+
149
+ def has_index?
150
+ @has_index
151
+ end
152
+
153
+ def has_uniq_index?
154
+ @has_uniq_index
155
+ end
156
+
157
+ def password_digest?
158
+ name == 'password' && type == :digest
159
+ end
160
+
161
+ def inject_options
162
+ "".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } }
163
+ end
164
+
165
+ def inject_index_options
166
+ has_uniq_index? ? ", unique: true" : ""
167
+ end
168
+ end
169
+ end
170
+ end
@@ -1,5 +1,6 @@
1
1
  require 'thor'
2
2
  require 'active_support/all'
3
+ require 'napa/cli/generated_attribute'
3
4
 
4
5
  module Napa
5
6
  module CLI
@@ -31,7 +32,7 @@ module Napa
31
32
  end
32
33
 
33
34
  def set_local_assigns!
34
- @migration_template = "migration"
35
+ @migration_template = "migration/migration.rb.tt"
35
36
  filename = migration_name.underscore
36
37
  case filename
37
38
  when /^(add|remove)_.*_(?:to|from)_(.*)/
@@ -46,14 +47,14 @@ module Napa
46
47
  end
47
48
  when /^create_(.+)/
48
49
  @table_name = $1.pluralize
49
- @migration_template = "create_table_migration"
50
+ @migration_template = "model/db/migrate/migration.rb.tt"
50
51
  end
51
52
  end
52
53
 
53
54
  def migration
54
- self.class.source_root "#{File.dirname(__FILE__)}/templates/#{@migration_template}"
55
+ self.class.source_root "#{File.dirname(__FILE__)}/templates/"
55
56
  say 'Generating migration...'
56
- directory '.', output_directory
57
+ template @migration_template, "#{output_directory}/#{migration_filename}.rb"
57
58
  say 'Done!', :green
58
59
  end
59
60
 
@@ -76,152 +77,5 @@ module Napa
76
77
  end.to_sym
77
78
  end
78
79
  end
79
-
80
- # directly ported from
81
- # https://github.com/rails/rails/blob/76883f92374c6395f13c16628e1d87d40b6d2399/railties/lib/rails/generators/generated_attribute.rb
82
- class GeneratedAttribute # :nodoc:
83
- INDEX_OPTIONS = %w(index uniq)
84
- UNIQ_INDEX_OPTIONS = %w(uniq)
85
-
86
- attr_accessor :name, :type
87
- attr_reader :attr_options
88
- attr_writer :index_name
89
-
90
- class << self
91
- def parse(column_definition)
92
- name, type, has_index = column_definition.split(':')
93
-
94
- # if user provided "name:index" instead of "name:string:index"
95
- # type should be set blank so GeneratedAttribute's constructor
96
- # could set it to :string
97
- has_index, type = type, nil if INDEX_OPTIONS.include?(type)
98
-
99
- type, attr_options = *parse_type_and_options(type)
100
- type = type.to_sym if type
101
-
102
- if type && reference?(type)
103
- references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { unique: true } : true
104
- attr_options[:index] = references_index
105
- end
106
-
107
- new(name, type, has_index, attr_options)
108
- end
109
-
110
- def reference?(type)
111
- [:references, :belongs_to].include? type
112
- end
113
-
114
- private
115
-
116
- # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to
117
- # when declaring options curly brackets should be used
118
- def parse_type_and_options(type)
119
- case type
120
- when /(string|text|binary|integer)\{(\d+)\}/
121
- return $1, limit: $2.to_i
122
- when /decimal\{(\d+)[,.-](\d+)\}/
123
- return :decimal, precision: $1.to_i, scale: $2.to_i
124
- when /(references|belongs_to)\{polymorphic\}/
125
- return $1, polymorphic: true
126
- else
127
- return type, {}
128
- end
129
- end
130
- end
131
-
132
- def initialize(name, type=nil, index_type=false, attr_options={})
133
- @name = name
134
- @type = type || :string
135
- @has_index = INDEX_OPTIONS.include?(index_type)
136
- @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type)
137
- @attr_options = attr_options
138
- end
139
-
140
- def field_type
141
- @field_type ||= case type
142
- when :integer then :number_field
143
- when :float, :decimal then :text_field
144
- when :time then :time_select
145
- when :datetime, :timestamp then :datetime_select
146
- when :date then :date_select
147
- when :text then :text_area
148
- when :boolean then :check_box
149
- else
150
- :text_field
151
- end
152
- end
153
-
154
- def default
155
- @default ||= case type
156
- when :integer then 1
157
- when :float then 1.5
158
- when :decimal then "9.99"
159
- when :datetime, :timestamp, :time then Time.now.to_s(:db)
160
- when :date then Date.today.to_s(:db)
161
- when :string then name == "type" ? "" : "MyString"
162
- when :text then "MyText"
163
- when :boolean then false
164
- when :references, :belongs_to then nil
165
- else
166
- ""
167
- end
168
- end
169
-
170
- def plural_name
171
- name.sub(/_id$/, '').pluralize
172
- end
173
-
174
- def singular_name
175
- name.sub(/_id$/, '').singularize
176
- end
177
-
178
- def human_name
179
- name.humanize
180
- end
181
-
182
- def index_name
183
- @index_name ||= if polymorphic?
184
- %w(id type).map { |t| "#{name}_#{t}" }
185
- else
186
- column_name
187
- end
188
- end
189
-
190
- def column_name
191
- @column_name ||= reference? ? "#{name}_id" : name
192
- end
193
-
194
- def foreign_key?
195
- !!(name =~ /_id$/)
196
- end
197
-
198
- def reference?
199
- self.class.reference?(type)
200
- end
201
-
202
- def polymorphic?
203
- self.attr_options.has_key?(:polymorphic)
204
- end
205
-
206
- def has_index?
207
- @has_index
208
- end
209
-
210
- def has_uniq_index?
211
- @has_uniq_index
212
- end
213
-
214
- def password_digest?
215
- name == 'password' && type == :digest
216
- end
217
-
218
- def inject_options
219
- "".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } }
220
- end
221
-
222
- def inject_index_options
223
- has_uniq_index? ? ", unique: true" : ""
224
- end
225
- end
226
80
  end
227
81
  end