Skip to content

Instantly share code, notes, and snippets.

@voscarmv
Last active September 9, 2021 18:50
Show Gist options
  • Save voscarmv/cf4b126eed800a087d2f20a15f04265a to your computer and use it in GitHub Desktop.
Save voscarmv/cf4b126eed800a087d2f20a15f04265a to your computer and use it in GitHub Desktop.
Scaffolding React/Redux for Ruby on Rails Api app. Sagas CRUD.

Articles

  1. https://medium.com/edonec/implement-redux-saga-in-your-react-typescript-project-1d79c4a2d726
  2. https://github.com/maprihoda/react-redux-crud
  3. https://blog.crunchydata.com/blog/generating-json-directly-from-postgres
  4. https://claritydev.net/blog/speed-up-your-react-developer-workflow-with-code-g/
  5. https://ejs.co/#install
  6. https://www.jondjones.com/frontend/react/npm-packages-for-react-developers/how-to-build-a-form-in-react-using-react-json-schema-form/

Plan

To generate sagas crud through the following pipeline:

  1. User generates postgresql database from rails db:create
  2. Database structure is exported as JSON. (Article 3.)
  3. JSON is ejs input. Ejs generates sagas crud (Articles 1., 2.).

I was going to use either plop or yeoman, but I decided not to because promps are an unnecessary hinderance.

I will write the templates and file distribution scripts myself.

Steps completed so far:

$ rails g scaffold testtypes binary:binary boolean:boolean date:date datetime:datetime decimal:decimal float:float integer:integer string:string text:text time:time timestamp:timestamp
  • Remember to switch to postgresql in database.yml and adding gem 'pg' to your Gemfile:
# SQLite. Versions 3.8.0 and up are supported.
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test

production:
  <<: *default
  database: db/production
  • From Article 3, using psql -d db/development on Ubuntu:
db/development=# \o database.json
db/development=#
WITH rows AS (
  SELECT c.relname, a.attname, a.attnotnull, a.attnum, t.typname
  FROM pg_class c
  JOIN pg_attribute a 
    ON c.oid = a.attrelid and a.attnum >= 0
  JOIN pg_type t
    ON t.oid = a.atttypid
  JOIN pg_namespace n
    ON c.relnamespace = n.oid
  WHERE n.nspname = 'public'
    AND c.relkind = 'r'
),                                  
agg AS (     
  SELECT rows.relname, json_agg(rows ORDER BY attnum) AS attrs
  FROM rows
  GROUP BY rows.relname
)                           
SELECT json_object_agg(agg.relname, agg.attrs)
FROM agg;
  • Then you can clean up the json with
$ cat database.json | sed '1,2d' | sed '$d' | sed '$d' | sed 's/+//g' > db.json
  • Result:
{
   "ar_internal_metadata":[
      {
         "relname":"ar_internal_metadata",
         "attname":"key",
         "attnotnull":true,
         "attnum":1,
         "typname":"varchar"
      },
      {
         "relname":"ar_internal_metadata",
         "attname":"value",
         "attnotnull":false,
         "attnum":2,
         "typname":"varchar"
      },
      {
         "relname":"ar_internal_metadata",
         "attname":"created_at",
         "attnotnull":true,
         "attnum":3,
         "typname":"timestamp"
      },
      {
         "relname":"ar_internal_metadata",
         "attname":"updated_at",
         "attnotnull":true,
         "attnum":4,
         "typname":"timestamp"
      }
   ],
   "schema_migrations":[
      {
         "relname":"schema_migrations",
         "attname":"version",
         "attnotnull":true,
         "attnum":1,
         "typname":"varchar"
      }
   ],
   "testtypes":[
      {
         "relname":"testtypes",
         "attname":"id",
         "attnotnull":true,
         "attnum":1,
         "typname":"int8"
      },
      {
         "relname":"testtypes",
         "attname":"updown",
         "attnotnull":false,
         "attnum":2,
         "typname":"bytea"
      },
      {
         "relname":"testtypes",
         "attname":"yesno",
         "attnotnull":false,
         "attnum":3,
         "typname":"bool"
      },
      {
         "relname":"testtypes",
         "attname":"appointment",
         "attnotnull":false,
         "attnum":4,
         "typname":"date"
      },
      {
         "relname":"testtypes",
         "attname":"event",
         "attnotnull":false,
         "attnum":5,
         "typname":"timestamp"
      },
      {
         "relname":"testtypes",
         "attname":"number",
         "attnotnull":false,
         "attnum":6,
         "typname":"numeric"
      },
      {
         "relname":"testtypes",
         "attname":"precise",
         "attnotnull":false,
         "attnum":7,
         "typname":"float8"
      },
      {
         "relname":"testtypes",
         "attname":"natural",
         "attnotnull":false,
         "attnum":8,
         "typname":"int4"
      },
      {
         "relname":"testtypes",
         "attname":"name",
         "attnotnull":false,
         "attnum":9,
         "typname":"varchar"
      },
      {
         "relname":"testtypes",
         "attname":"story",
         "attnotnull":false,
         "attnum":10,
         "typname":"text"
      },
      {
         "relname":"testtypes",
         "attname":"due",
         "attnotnull":false,
         "attnum":11,
         "typname":"time"
      },
      {
         "relname":"testtypes",
         "attname":"limit",
         "attnotnull":false,
         "attnum":12,
         "typname":"timestamp"
      },
      {
         "relname":"testtypes",
         "attname":"created_at",
         "attnotnull":true,
         "attnum":13,
         "typname":"timestamp"
      },
      {
         "relname":"testtypes",
         "attname":"updated_at",
         "attnotnull":true,
         "attnum":14,
         "typname":"timestamp"
      }
   ]
}
  • Now we can transform this into a React form (and later a React functional component with hooks), by using react-json-schema-form (article 6.). I start by writing an .ejs script to generate the widget correspondence hash:
const formCorrespondence = {<% testtypes.forEach((i, t) => { %>
    '<%= i.attname %>': { 'type':'<%= i.typname %>', 'form':'template.ejs' },<% }); %>
};
  • The result of npx ejs string.ejs -f db.json is:
const formCorrespondence = {
    'id': { 'type':'int8', 'form':'template.ejs' },
    'binary': { 'type':'bytea', 'form':'template.ejs' },
    'boolean': { 'type':'bool', 'form':'template.ejs' },
    'date': { 'type':'date', 'form':'template.ejs' },
    'datetime': { 'type':'timestamp', 'form':'template.ejs' },
    'decimal': { 'type':'numeric', 'form':'template.ejs' },
    'float': { 'type':'float8', 'form':'template.ejs' },
    'integer': { 'type':'int4', 'form':'template.ejs' },
    'string': { 'type':'varchar', 'form':'template.ejs' },
    'text': { 'type':'text', 'form':'template.ejs' },
    'time': { 'type':'time', 'form':'template.ejs' },
    'timestamp': { 'type':'timestamp', 'form':'template.ejs' },
    'created_at': { 'type':'timestamp', 'form':'template.ejs' },
    'updated_at': { 'type':'timestamp', 'form':'template.ejs' },
};
  • This is the formify.ejs generator that turns db.json into a react-json-schema-form form object:
[
<% const formCorrespondence = {
    'int8': { 'ARtype':'id', 'form':'integer' },
    'bytea': { 'ARtype':'binary', 'form':'radio' },
    'bool': { 'ARtype':'boolean', 'form':'checkbox' },
    'date': { 'ARtype':'date', 'form':'date' },
    'timestamp': { 'ARtype':'datetime', 'form':'datetime' },
    'numeric': { 'ARtype':'decimal', 'form':'number' },
    'float8': { 'ARtype':'float', 'form':'number' },
    'int4': { 'ARtype':'integer', 'form':'integer' },
    'varchar': { 'ARtype':'string', 'form':'string' },
    'text': { 'ARtype':'text', 'form':'text' },
    'time': { 'ARtype':'time', 'form':'time' },
};
%>
<% for(const table in data) { %>
{
    "title": "<%= table %>",
    "description": "A simple form example.",
    "type": "object",
    "properties": {
    <% data[table].forEach((f, i)=>{ %>
        <%- include(`./${formCorrespondence[f.typname].form}`, {name: f.attname}) %>
    <% }); %>
    }
},
<% } %>
]
  • The output of npx ejs formify.ejs -f db2.json > jsonforms.json is
[
   {
      "title":"ar_internal_metadata",
      "description":"A simple form example.",
      "type":"object",
      "properties":{
         "key":{
            "type":"string",
            "title":"key"
         },
         "value":{
            "type":"string",
            "title":"value"
         },
         "created_at":{
            "type":"string",
            "format":"date-time",
            "title":"created_at"
         },
         "updated_at":{
            "type":"string",
            "format":"date-time",
            "title":"updated_at"
         }
      }
   },
   {
      "title":"schema_migrations",
      "description":"A simple form example.",
      "type":"object",
      "properties":{
         "version":{
            "type":"string",
            "title":"version"
         }
      }
   },
   {
      "title":"testtypes",
      "description":"A simple form example.",
      "type":"object",
      "properties":{
         "id":{
            "title":"id",
            "type":"integer"
         },
         "binary":{
            "type":"boolean",
            "title":"binary",
            "description":""
         },
         "boolean":{
            "type":"boolean",
            "title":"boolean",
            "description":""
         },
         "date":{
            "type":"string",
            "format":"date",
            "title":"date"
         },
         "datetime":{
            "type":"string",
            "format":"date-time",
            "title":"datetime"
         },
         "decimal":{
            "title":"decimal",
            "type":"number"
         },
         "float":{
            "title":"float",
            "type":"number"
         },
         "integer":{
            "title":"integer",
            "type":"integer"
         },
         "string":{
            "type":"string",
            "title":"string"
         },
         "text":{
            "type":"string",
            "title":"text"
         },
         "time":{
            "type":"string",
            "format":"date-time",
            "title":"time"
         },
         "timestamp":{
            "type":"string",
            "format":"date-time",
            "title":"timestamp"
         },
         "created_at":{
            "type":"string",
            "format":"date-time",
            "title":"created_at"
         },
         "updated_at":{
            "type":"string",
            "format":"date-time",
            "title":"updated_at"
         }
      }
   }
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment