fae-rails 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/docs/index.md ADDED
@@ -0,0 +1,131 @@
1
+ # Fae Installation and Customization
2
+
3
+ * [Installation](#installation)
4
+ * [Dependencies](#dependencies)
5
+ * [Installer](#faeinstall)
6
+ * [Seeding](#db-seed)
7
+ * [Versioning](#version-management)
8
+ * [Initializer](#fae-initializer)
9
+ * [Mailer Configuration](#devise-action-mailer-configuration)
10
+
11
+ ---
12
+
13
+ # Installation
14
+
15
+ Add the gem to your Gemfile and run `bundle install`
16
+
17
+ ```ruby
18
+ gem 'fae-rails'
19
+ ```
20
+ Run the installer
21
+
22
+ ```bash
23
+ $ rails g fae:install
24
+ ```
25
+
26
+ After the installer completes, visit `/admin` and setup your first user account. That should automatically log you in to your blank Fae instance.
27
+
28
+ ## Dependencies
29
+
30
+ ### Rails
31
+
32
+ Fae supports Rails >= 4.1.
33
+
34
+ ### Sass and sass-rails
35
+
36
+ Fae requires `sass >= 3.4` and `sass-rails >= 5`.
37
+
38
+ If you're using Rails 4.1 you'll need to update the versions in the `Gemfile`:
39
+
40
+ ```ruby
41
+ gem 'sass-rails', '~> 5.0.0'
42
+ gem 'sass', '~> 3.4.0'
43
+ ```
44
+
45
+ and run:
46
+
47
+ ```bash
48
+ $ bundle update sass-rails
49
+ $ bundle update sass
50
+ ```
51
+
52
+ ## fae:install
53
+
54
+ Fae's installer will do the following:
55
+
56
+ - add Fae's namespace and route to `config/routes.rb`
57
+ - add `app/assets/stylesheets/fae.scss` for UI color management and custom CSS
58
+ - add `app/assets/javascripts/fae.js` for custom JS
59
+ - add `app/controllers/concerns/fae/nav_items.rb` to manage main navigation
60
+ - add Fae's initializer: `config/initializers/fae.rb`
61
+ - add `config/initializers/judge.rb` for validation config
62
+ - copies over migrations from Fae
63
+ - runs `rake db:migrate`
64
+ - seeds the DB with Fae defaults
65
+
66
+ ## DB Seed
67
+
68
+ Fae comes with a rake task to seed the DB with defaults:
69
+
70
+ ```bash
71
+ rake fae:seed_db RAILS_ENV=<your_env>
72
+ ```
73
+
74
+ If you ran the installer, the task will be run automatically. But if you are setting up an established Fae instance locally or deploying to a server, running this will get you setup with some defaults.
75
+
76
+ ## Version management
77
+
78
+ Fae follows semantic versioning, so you can expect the following format: `major.minor.patch`. Patch versions add bugfixes, minor versions add backwards compilable features and major versions add non-backward compatible features.
79
+
80
+ ---
81
+
82
+ # Fae Initializer
83
+
84
+ Fae's default config can be overwritten in a `config/initializers/fae.rb` file.
85
+
86
+ | key | type | default | description |
87
+ | --- | ---- | ------- | ----------- |
88
+ | devise_secret_key | string | | unique Devise hash |
89
+ | devise_mailer_sender | string | change-me@example.com | address used to send Devise notifications (i.e. forgot password emails) |
90
+ | dashboard_exclusions | array | [] | array of models to hide in the dashboard |
91
+ | max_image_upload_size | integer | 2 | ceiling for image uploads in MB |
92
+ | max_file_upload_size | integer | 5 | ceiling for file uploads in MB |
93
+ | recreate_versions | boolean | false | Triggers `Fae::Image` to recreate Carrierwave versions after save. This is helpful when you have conditional versions that rely on attributes of `Fae::Image` by making sure they're saved before versions are created. |
94
+ | track_changes | boolean | true | Determines whether or not to track changes on your objects |
95
+ | tracker_history_length | integer | 15 | Determines the max number of changes logged per object |
96
+
97
+ ### Example
98
+
99
+ ```ruby
100
+ Fae.setup do |config|
101
+
102
+ config.devise_secret_key = '79a3e96fecbdd893853495ff502cd387e22c9049fd30ff691115b8a0b074505be4edef6139e4be1a0a9ff407442224dbe99d94986e2abd64fd0aa01153f5be0d'
103
+
104
+ # models to exclude from dashboard list
105
+ config.dashboard_exclusions = %w( Varietal )
106
+
107
+ end
108
+ ```
109
+
110
+ ---
111
+
112
+ # Devise Action Mailer Configuration
113
+
114
+ In order for Fae's password reset email function to work you need to make sure you application can send mail and set a default url option for ActionMailer in each `config/environments/*env.rb` file.
115
+
116
+ ## Example
117
+
118
+ ```ruby
119
+ Rails.application.configure do
120
+ # development.rb
121
+ config.action_mailer.default_url_options = { host: 'localhost:3000' }
122
+ # remote_development.rb
123
+ config.action_mailer.default_url_options = { host: 'dev.yoursite.com' }
124
+ # stage.rb
125
+ config.action_mailer.default_url_options = { host: 'stage.yoursite.com' }
126
+ # production.rb
127
+ config.action_mailer.default_url_options = { host: 'yoursite.com' }
128
+ end
129
+ ```
130
+
131
+ Be sure to update this each time your domain or subdomain changes.
@@ -0,0 +1,21 @@
1
+ # JavaScript Standards
2
+
3
+ ## Functions
4
+
5
+ camelCase
6
+
7
+ ### Access
8
+
9
+ Methods used only within the namespace should be preceeded by an underscore. This denotes the equivalent of Ruby's `private` even those these functions are still publicly exposed.
10
+
11
+ ## Variables
12
+
13
+ snake_case
14
+
15
+ ### References
16
+
17
+ When referencing upper namespace, always use `_this`. `that` will not be tolerated.
18
+
19
+ ### jQuery objects
20
+
21
+ Preceed all things jQuery with a `$`.
data/docs/upgrading.md ADDED
@@ -0,0 +1,28 @@
1
+ # Upgrading Fae
2
+
3
+ * [From v1.1 to v1.2](#from-v11-to-v12)
4
+ * [From v1.0 to v1.1](#from-v10-to-v11)
5
+
6
+ ---
7
+
8
+ # From v1.1 to v1.2
9
+
10
+ Fae v1.2 adds a new table `Fae::Change` to track changes on your objects. After updating you'll have to copy over and run the new migrations.
11
+
12
+ ```bash
13
+ $ rake fae:install:migrations
14
+ $ rake db:migrate
15
+ ```
16
+
17
+ ---
18
+
19
+ # From v1.0 to v1.1
20
+
21
+ [View the CHANGELOG](changelog.md#markdown-header-11)
22
+
23
+ Fae v1.1 adds a new column to `Fae::User`. After upgrading you'll have to copy over the new migration and run it.
24
+
25
+ ```bash
26
+ $ rake fae:install:migrations
27
+ $ rake db:migrate
28
+ ```
data/docs/usage.md ADDED
@@ -0,0 +1,627 @@
1
+ # Usage
2
+
3
+ * [Generators](#generators)
4
+ * [Models](#models)
5
+ * [Nested Resources](#nested-resources)
6
+ * [Validation](#validation)
7
+ * [Image and File Associations](#image-and-file-associations)
8
+ * [Controllers](#controllers)
9
+ * [Navigation Items](#navigation-items)
10
+ * [Form Helpers](#form-helpers)
11
+ * [Pages and Content Blocks](#pages-and-content-blocks)
12
+
13
+ ---
14
+
15
+ # Generators
16
+
17
+ Once you have Fae installed, you're ready to start generating your data model. Fae comes with a few generators that work similarly to the ones in Rails. The idea is scaffolding a model with these generators will give you a section to create, edit and delete objects.
18
+
19
+ ## fae:scaffold
20
+
21
+ ```ruby
22
+ rails g fae:scaffold [ModelName] [field:type] [field:type]
23
+ ```
24
+ | option | description |
25
+ |------- | ----------- |
26
+ | ModelName | singular camel-cased model name |
27
+ | field | the attributes column name |
28
+ | type | the column type (defaults to `string`), find all options in [Rails' documentaion](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column) |
29
+
30
+ This is Fae's main generator. It will create the following:
31
+
32
+ - model
33
+ - controller and views for fully CRUDable section
34
+ - migration
35
+ - resource routes
36
+ - link in `app/controllers/concerns/fae/nav_items.rb`
37
+
38
+ ### Special Attributes
39
+
40
+ **name**/**title** will automatically be set as the model's `fae_display_field`.
41
+
42
+ **position** will automatically make the section's index table sortable, be ignored from the form and add acts_as_list and default_scope to the model.
43
+
44
+ **on_prod**/**on_stage**/**active** will automatically be flag fields in the section's index and ignored in the form.
45
+
46
+ **_id**/**:references** will automatically be setup as an association in the form.
47
+
48
+ ### Example
49
+
50
+ ```bash
51
+ rails g fae:scaffold Person first_name last_name title body:text date_of_birth:date position:integer on_stage:boolean on_prod:boolean group:references
52
+ ```
53
+
54
+
55
+ ## fae:nested_scaffold
56
+
57
+ ```bash
58
+ rails g fae:nested_scaffold [ModelName] [field:type] [field:type] [--parent-model=ParentModel]
59
+ ```
60
+
61
+ | option | description |
62
+ | ------ | ----------- |
63
+ | `[--parent-model=ParentModel]` | an optional flag that adds the association to the generated model.|
64
+
65
+ The nested scaffold creates a model that will be nested in another object's form via the `nested_table_advanced` partial. This generator is very similar to `fae:scaffold`, the main difference is in the views that are setup to serve the nested form.
66
+
67
+
68
+ ## fae:nested_index_scaffold
69
+
70
+ ```bash
71
+ rails g fae:nested_index_scaffold [ModelName] [field:type] [field:type]
72
+ ```
73
+
74
+ The nested index scaffold creates a normal model and a controller that supports the nested_index_form partial. This generator is very similar to `fae:scaffold`, the main difference is in the views that are setup to serve the nested form.
75
+
76
+ ## fae:page
77
+
78
+ ```bash
79
+ rails g fae:page [PageName] [field:type] [field:type]
80
+ ```
81
+
82
+ | option | description |
83
+ |-----------|-------------|
84
+ | PageName | the name of the page |
85
+ | field | the name of the content block |
86
+ | type | the type of the content block (see table below) |
87
+
88
+ | content block | associated object |
89
+ |---------------|-------------------|
90
+ | string | Fae::TextField |
91
+ | text | Fae::TextArea |
92
+ | image | Fae::Image |
93
+ | file | Fae::File |
94
+
95
+ The page generator scaffolds a page into Fae's content blocks system. More on that later, for now here's what it does:
96
+
97
+ - creates or adds to `app/controllers/admin/content_blocks_controller.rb`
98
+ - creates a `#{page_name}_page.rb` model
99
+ - creates a form view in `app/views/admin/content_blocks/#{page_name}.html.slim`
100
+
101
+ ### Example
102
+
103
+ ```bash
104
+ rails g fae:page AboutUs title:string introduction:text body:text header_image:image
105
+ ```
106
+
107
+ ---
108
+
109
+ # Models
110
+
111
+ A generated model will start off with sensible defaults based on the attributes you used in the generator. Here are some common custom additions you should be aware of.
112
+
113
+ ## Fae's Base Model Concern
114
+
115
+ To allow Fae to push out any model specific updates to your application models, include the concern at the top of the class body:
116
+
117
+ ```ruby
118
+ class Release < ActiveRecord::Base
119
+ include Fae::BaseModelConcern
120
+ # ...
121
+ end
122
+ ```
123
+
124
+ ## fae_display_field
125
+
126
+ Fae uses `fae_display_field` in a our table views. Defining it as a class method that returns the value of one or multiple attributes is required for those tables to display properly.
127
+
128
+ If the model is generated, then it will use `name` or `title` by default.
129
+
130
+ ### Examples
131
+
132
+ ```ruby
133
+ def fae_display_field
134
+ title
135
+ end
136
+ ```
137
+
138
+ ```ruby
139
+ def fae_display_field
140
+ "#{last_name}, #{first_name}"
141
+ end
142
+ ```
143
+
144
+ ## for_fae_index
145
+
146
+ Fae uses a class method called `for_fae_index` as a scope for index views and associated content in form elements. This method is inherited from `Fae::BaseModelConcern`.
147
+
148
+ By default, this method uses position, name, or title attributes. If it can't find any of those it will raise the following exception:
149
+
150
+ ```
151
+ No order_method found, please define for_fae_index as a #{model_name} class method to set a custom scope.
152
+ ```
153
+
154
+ To override the default or get rid of this exception, simple define the class method in your model:
155
+
156
+ ```ruby
157
+ def self.for_fae_index
158
+ order(:first_name)
159
+ end
160
+ ```
161
+
162
+ ## to_csv
163
+
164
+ Fae uses a class method called `to_csv` as a method to export all the objects related to a given model to a csv. This method is inherited from `Fae::BaseModelConcern`. It is meant to be called from the index action.
165
+
166
+
167
+ ## Nested Resources
168
+
169
+ If you use nested resource routes and want updates on those objects to show up in the dashboard, you'll need to define it's parent for Fae to know how to link them.
170
+
171
+ To do this, add a class method called `fae_parent` pointing to the underscored association to the parent object. Here is an example:
172
+
173
+ routes.rb
174
+ ```ruby
175
+ namespace :admin do
176
+ resources :groups do
177
+ resources :people
178
+ end
179
+ end
180
+ ```
181
+
182
+ models/person.rb
183
+ ```ruby
184
+ # if this is the parent
185
+ belongs_to :group
186
+
187
+ # then you'll define this
188
+ def fae_parent
189
+ group
190
+ end
191
+ ```
192
+
193
+ ## Validation
194
+
195
+ Fae doesn't deal with any validation definitions in your application models, you'll have to add those. However, there are some pre-defined regex validation helpers to use in your models. See examples below.
196
+
197
+ ### Validation Helpers
198
+
199
+ Fae validation helpers come in two flavors; regex only, and complete hash.
200
+
201
+ Regex:
202
+
203
+ | option | description |
204
+ |---------------|---------------------------------------------------------|
205
+ | slug_regex | no spaces or special characters |
206
+ | email_regex | valid email with @ and . |
207
+ | url_regex | http and https urls |
208
+ | zip_regex | 5 digit zip code |
209
+ | youtube_regex | matches youtube id, i.e. the 11 digits after "watch?v=" |
210
+
211
+ example:
212
+
213
+ ```ruby
214
+ validates :slug,
215
+ uniqueness: true,
216
+ presence: true,
217
+ format: {
218
+ with: Fae.validation_helpers.slug_regex,
219
+ message: 'no spaces or special characters'
220
+ }
221
+ ```
222
+
223
+ Complete:
224
+
225
+ | option | description |
226
+ |--------------|--------------------------------------------------|
227
+ | slug | uniqueness, presence, regex format with message |
228
+ | email | regex format with message, allow blank |
229
+ | url | regex form with message, allow blank |
230
+ | zip | regex format with message, allow blank |
231
+ | youtube_url | regex format with message, allow blank |
232
+
233
+ example:
234
+
235
+ ```ruby
236
+ validates :slug, Fae.validation_helpers.slug
237
+ ```
238
+
239
+ ### Judge and Uniqueness
240
+
241
+ Fae uses [Judge](https://github.com/joecorcoran/judge) to automatically add client side validation from the declarations in the models. The caveat is Judge requires you to expose any attributes that have a uniqueness validation. You can do this in `config/initializers/jugde.rb`:
242
+
243
+ ```ruby
244
+ Judge.configure do
245
+ expose Person, :slug
246
+ expose Wine, :name, :slug
247
+ end
248
+ ```
249
+
250
+ ## Image and File Associations
251
+
252
+ Fae provides models for images and files: `Fae::Image` and `Fae::File` respectively. These models come with their own attributes, validations and uploaders and can be polymorphically associated to your application models.
253
+
254
+ Here's a basic example:
255
+
256
+ ```ruby
257
+ has_one :bottle_shot, -> { where(attached_as: 'bottle_shot') },
258
+ as: :imageable,
259
+ class_name: '::Fae::Image',
260
+ dependent: :destroy
261
+ accepts_nested_attributes_for :bottle_shot, allow_destroy: true
262
+ ```
263
+
264
+ Here's the breakdown:
265
+
266
+ `has_one :bottle_shot` sets the name of the custom association.
267
+
268
+ `-> { where(attached_as: 'bottle_shot') }` sets the scope of the association. If we have more than one `Fae::Image` we need to set the `attached_as` to distinguish it from other images associated to that model.
269
+
270
+ `as: :imageable, class_name: '::Fae::Image'` defines the polymorphic association.
271
+
272
+ `dependent: :destroy` will make sure the image object is destroyed along with the parent object.
273
+
274
+ `accepts_nested_attributes_for :bottle_shot, allow_destroy: true` allows the image/file uploader to be nested in the parent object's form in Fae.
275
+
276
+ ### Other Examples
277
+
278
+ An onject with many gallery images:
279
+
280
+ ```ruby
281
+ has_many :gallery_images, -> { where(attached_as: 'gallery_images') },
282
+ as: :imageable,
283
+ class_name: '::Fae::Image',
284
+ dependent: :destroy
285
+ accepts_nested_attributes_for :gallery_images, allow_destroy: true
286
+ ```
287
+
288
+ A file example:
289
+
290
+ ```ruby
291
+ has_one :tasting_notes_pdf, -> { where(attached_as: 'tasting_notes_pdf') },
292
+ as: :fileable,
293
+ class_name: '::Fae::File',
294
+ dependent: :destroy
295
+ accepts_nested_attributes_for :tasting_notes_pdf, allow_destroy: true
296
+ ```
297
+
298
+ If the object only has one image association, you can get away with omitting the scope:
299
+
300
+ ```ruby
301
+ has_one :image, as: :imageable, class_name: '::Fae::Image', dependent: :destroy
302
+ accepts_nested_attributes_for :image, allow_destroy: true
303
+ ```
304
+
305
+ ---
306
+
307
+ # Controllers
308
+
309
+ Controllers that manage models in Fae should be namespaced and inherit from `Fae::BaseController`. Controllers that are generated have this already in place:
310
+
311
+ ```ruby
312
+ module Admin
313
+ class PeopleController < Fae::BaseController
314
+ # ...
315
+ end
316
+ end
317
+ ```
318
+ For a standard Fae section you can pretty much leave your controller empty. Most of the magic happens in [Fae::BaseController](../app/controllers/fae/base_controller.rb). But there are a few things you should know about.
319
+
320
+ ## Building Assets
321
+
322
+ If the section manages objects with associated images or files, you'll need to build those objects by overriding the `build_assets` private method.
323
+
324
+ ```ruby
325
+ module Admin
326
+ class WinesController < Fae::BaseController
327
+
328
+ private
329
+
330
+ def build_assets
331
+ @item.build_bottle_shot if @item.bottle_shot.blank?
332
+ @item.build_label_pdf if @item.label_pdf.blank?
333
+ end
334
+ end
335
+ end
336
+ ```
337
+
338
+ ## Custom Titles in Views
339
+
340
+ If you'd like to change the generated titles in the Fae views, you can do so with the following `before_action`.
341
+
342
+ ```ruby
343
+ module Admin
344
+ class WinesController < Fae::BaseController
345
+ before_action do
346
+ set_class_variables 'Vinos'
347
+ end
348
+ end
349
+ end
350
+ ```
351
+
352
+ This will affect the add button text and index/form page titles.
353
+
354
+ ---
355
+
356
+ # Navigation Items
357
+
358
+ When you use the generators, a link to the section appears in the main navigation of the admin. This is done by automatically adding to `app/controllers/concerns/fae/nav_items.rb`. However, this file is available for you to customize the nav however you'd like.
359
+
360
+ The navigation is built of of the array set in the `nav_items` method. Each array item is a hash with these available keys:
361
+
362
+ | Key | Type | Description |
363
+ | --- | ---- | ----------- |
364
+ | text | string | the link's text |
365
+ | path | string or named route | the link's href (defaults to '#') |
366
+ | class | string | an added class to the link |
367
+ | sublinks | array of hashes | nested links to be displayed in a dropdown |
368
+
369
+ ## Named Routes in nav_items.rb
370
+
371
+ Since the `nav_items` concern hooks directly into Fae, named routes need context using the following prefixes:
372
+
373
+ ```ruby
374
+ def nav_items
375
+ [
376
+ # use `main_app.` for application routes (even in your admin)
377
+ { text: 'Cities', path: main_app.admin_cities_path },
378
+ # use `fae.` for Fae routes
379
+ { text: 'Pages', path: fae.pages_path }
380
+ ]
381
+ end
382
+ ```
383
+
384
+ ## Sublinks
385
+
386
+ When sublinks are present, the main nav item will trigger a drawer holding the sublinks to open/close. Add sublinks using the following format:
387
+
388
+ ```ruby
389
+ def nav_items
390
+ [
391
+ {
392
+ text: 'Items with sublinks', sublinks: [
393
+ { text: 'Item Sublink 1', path: main_app.admin_some_path },
394
+ { text: 'Item Sublink 2', path: main_app.admin_someother_path }
395
+ ]
396
+ }
397
+ ]
398
+ end
399
+ ```
400
+
401
+ ## Dynamic Content in Nav
402
+
403
+ Dynamic content is allowed in the the `nav_items` concern. Here's an example:
404
+
405
+ ```ruby
406
+ module Fae
407
+ module NavItems
408
+ extend ActiveSupport::Concern
409
+
410
+ def nav_items
411
+ [
412
+ { text: 'Releases', path: main_app.admin_releases_path },
413
+ { text: 'Tiers', sublinks: tier_sublinks }
414
+ ]
415
+ end
416
+
417
+ private
418
+
419
+ def tier_sublinks
420
+ tiers_arr = [{ text: 'New Tier', path: main_app.new_admin_tier_path }]
421
+ Tier.each do |tier|
422
+ tiers_arr << { text: tier.name, path: main_app.edit_admin_tier_path(tier) }
423
+ end
424
+ tiers_arr
425
+ end
426
+
427
+ end
428
+ end
429
+ ```
430
+
431
+ ---
432
+
433
+ # Form Helpers
434
+ [Click here for full documentation on Fae's form helpers](helpers.md#markdown-header-form-helpers)
435
+
436
+ Generated forms start you off on a good place to manage the object's content, but chances are you'll want to customize them and add more fields as you data model evolves. Fae provides a number of form helpers to help you leverage Fae's built in features will allowing customization when needed.
437
+
438
+ Form helpers in Fae use the [simple_form](https://github.com/plataformatec/simple_form) gem as it's base. In most cases options that simple_form accepts can be passed into these helpers directly. The reason why we've established these helpers it to allow for customized options. They also provide a method to directly hook into Fae, so we can push out features and bugfixes.
439
+
440
+ ---
441
+
442
+ # View Helpers and Partials
443
+
444
+ Fae also provides a number of other built in view helpers and partials, that are documented in [helpers.md](helpers.md).
445
+
446
+ [Click here for view helpers](helpers.md#markdown-header-view-helpers)
447
+ [Click here for Fae partials](helpers.md#markdown-header-fae-partials)
448
+
449
+ ---
450
+
451
+ # Custom Helpers
452
+
453
+ If you want to add your own helper methods for your Fae views, simply create and add them to `app/helpers/fae/fae_helper.rb`.
454
+
455
+ ```ruby
456
+ module Fae
457
+ module FaeHelper
458
+ # ...
459
+ end
460
+ end
461
+ ```
462
+
463
+ ---
464
+
465
+ # Cloning
466
+
467
+ [Click here for full documentation on Fae's cloning](cloning.md)
468
+
469
+ Many users find it easier to clone records that have similar content, rather than spending the time manually setting them up. Fae has the ability to allow one-click clones from the index or the edit form, as well as flexibility to whitelist attributes and clone assocations.
470
+
471
+ ---
472
+
473
+ # Pages and Content Blocks
474
+
475
+ Fae has a built in system to handle content blocks that are statically wired to pages in your site. This is for content that isn't tied to an object in your data model, e.g. home, about and terms content.
476
+
477
+ The system is just your basic inherited singleton with dynamic polymorphic associations. Kidding aside, the complexity of the system is hidden and "it just works™" if you use the generators and/or follow the conventions. This allows for dynamic content blocks that can be added without database migrations and wired up without static IDs!
478
+
479
+ ## Pages vs Content Blocks
480
+
481
+ **Pages** are groups of **content blocks** based on the actual pages they appear on the site. For the following example, we will use a page called `AboutUs`, which will have content blocks for `hero_image`, `title`, `introduction`, `body` and `annual_report`.
482
+
483
+ ## Generating Pages
484
+
485
+ It is highly recommended you use the built in generator to add pages, especially if it's the first page in the admin. Let's do that for our example:
486
+
487
+ ```bash
488
+ rails g fae:page AboutUs hero_image:image hero_text:string introduction:text body:text annual_report:file
489
+ ```
490
+
491
+ This will generate...
492
+
493
+ `app/models/about_us_page.rb`
494
+ ```ruby
495
+ class AboutUsPage < Fae::StaticPage
496
+
497
+ @slug = 'about_us'
498
+
499
+ # required to set the has_one associations, Fae::StaticPage will build these associations dynamically
500
+ def self.fae_fields
501
+ {
502
+ hero_image: { type: Fae::Image },
503
+ hero_text: { type: Fae::TextField },
504
+ introduction: { type: Fae::TextArea },
505
+ body: { type: Fae::TextArea },
506
+ annual_report: { type: Fae::File }
507
+ }
508
+ end
509
+
510
+ end
511
+ ```
512
+
513
+ `app/views/fae/pages/about_us.html.slim`
514
+ ```ruby
515
+ = simple_form_for @item, url: fae.update_content_block_path(slug: @item.slug), method: :put do |f|
516
+ section.main_content-header
517
+ .main_content-header-wrapper
518
+ = render 'fae/shared/form_header', header: @item
519
+ = render 'fae/shared/form_buttons', f: f
520
+
521
+ .main_content-sections
522
+ section.main_content-section
523
+ .main_content-section-area
524
+ = fae_input f, :title
525
+
526
+ = fae_image_form f, :hero_image
527
+ = fae_content_form f, :hero_text
528
+ = fae_content_form f, :introduction
529
+ = fae_content_form f, :body
530
+ = fae_file_form f, :annual_report
531
+ ```
532
+
533
+ Since this is the first page the generator will create `app/controllers/admin/content_blocks_controller.rb`, otherwise it would just add to the `fae_pages` array.
534
+
535
+
536
+ ```ruby
537
+ module Admin
538
+ class ContentBlocksController < Fae::StaticPagesController
539
+
540
+ private
541
+
542
+ def fae_pages
543
+ [AboutUsPage]
544
+ end
545
+ end
546
+ end
547
+ ```
548
+
549
+ ## Adding Content Blocks
550
+
551
+ Chances are you'll need to add content blocks to a page after it's been generated. To do so simply:
552
+
553
+ - add the new content blocks to `fae_fields` in the `AboutUsPage` model
554
+ - add the appropriate form elements to the form at `about_us.html.slim`
555
+ - `fae_content_form` for `Fae::TextField` and `Fae::TextArea`
556
+ - `fae_image_form` for `Fae::Image`
557
+ - `fae_file_form` for `Fae::File`
558
+
559
+ ## Getting Your Content Blocks
560
+
561
+ Each page generated is a singleton model and each content block is an association to a Fae model.
562
+
563
+ To get an instance of your page:
564
+
565
+ ```ruby
566
+ @about_us_page = AboutUsPage.instance
567
+ ```
568
+
569
+ Then to get content from a `Fae::TextField` and `Fae::TextArea`:
570
+
571
+ ```ruby
572
+ # for `Fae::TextField` or `Fae::TextArea`
573
+ @about_us_page.hero_text.content
574
+ # ... or ...
575
+ @about_us_page.hero_text_content
576
+
577
+ # for `Fae::Image` or `Fae::File`
578
+ @about_us_page.hero_image.asset.url
579
+ # for `Fae::Image` only
580
+ @about_us_page.hero_image.asset.alt
581
+ @about_us_page.hero_image.asset.caption
582
+ ```
583
+
584
+ ## Invalid Content Block Names
585
+
586
+ Content blocks are just associations on the page model, which inherits from `Fae::Page`. Because of this, attributes on `Fae::Page` are invalid names for content blocks. These attributes are:
587
+
588
+ - title
589
+ - slug
590
+ - position
591
+ - on_prod
592
+ - on_stage
593
+ - created_at
594
+ - updated_at
595
+
596
+ ## Validations on Content Blocks
597
+
598
+ Since content blocks are setup as associations, adding validations to them can be tricky. To make it easier we setup a method directly in `fae_fields` hash that will dynamically add the validations to the appropriate model and apply the `data-validate` attribute in the form so Judge can do it's best to validate the content on the frontend.
599
+
600
+ To add validations to a content block, add a validates option with your rules on a specific content block in `fae_fields`. Format the rules just as you would normal model validations.
601
+
602
+ `app/models/about_us_page.rb`
603
+ ```ruby
604
+ def self.fae_fields
605
+ {
606
+ hero_image: { type: Fae::Image },
607
+ hero_text: {
608
+ type: Fae::TextField,
609
+ validates: { presence: true }
610
+ },
611
+ introduction: {
612
+ type: Fae::TextArea,
613
+ validates: {
614
+ presence: true,
615
+ length: {
616
+ maximum: 100,
617
+ message: 'should be brief (100 characters or less)'
618
+ }
619
+ },
620
+ },
621
+ body: { type: Fae::TextArea },
622
+ annual_report: { type: Fae::File }
623
+ }
624
+ end
625
+ ```
626
+
627
+ Validations can only be applied to types `Fae::TextField` and `Fae::TextArea`.