napa 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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