create-react-appでejectせずにmarkdownをレンダリングする

TL;DR

  • raw.macroはraw loaderをwebpack confに記述しなくても使えるようなしたもの
  • raw.macroでローカルのmdを読み込み,markedなどのmarkdownパーサーでパースすることでレンダリングすることができる. github.com github.com

raw.macroの使い方

何かしらを書いたtest.txtを保存し,test.txtのpathをraw.macroの引数に与えることでtest.txtに記述した内容が読み込まれます.

import React from "react";
import macro from "raw.macro";

function App() {
  return <div className="App">{macro("./test.txt")}</div>;
}

export default App;

raw.macro + marked

markdown parserであるmarkedをインストールします

npm install marked
import React from "react";
import raw from "raw.macro";
import marked from "marked";

const renderer = new marked.Renderer();
const markedOptions = {
    gfm: true,
    tables: true,
    breaks: true,
    pedantic: false,
    sanitize: false,
    smartLists: true,
    smartypants: false,
    renderer
};
function App() {
  return <div className="App">
      <div
          dangerouslySetInnerHTML={{
              __html: marked(raw("./article.md"), markedOptions)
          }}
      />
  </div>;
}

export default App;

markdownがhtmlにパースできていることがわかると思います. f:id:igrrk:20190516163457j:plain

Gem,Bundler,Gemfile,Gemfile.lockについて

gem,bundler,Gemfile,Gemfile.lockの違いを整理したいと思います.

RubyGemsとGem

The RubyGems software allows you to easily download, install, and use ruby software packages on your system. The software package is called a “gem” which contains a packaged Ruby application or library.

RubyGemsソフトウェアはRubyのパッケージを簡単にダウンロード,インストール,及び使用をすることができます. パッケージング化されたアプリケーションやライブラリを含んだパッケージをgemと呼びます.

Bundler

Bundler provides a consistent environment for Ruby projects by tracking and installing the exact gems and versions that are needed. Bundlerはプロジェクトで必要とされている特定のGemとバージョンを追跡し,インストールを行うことで一貫した環境を提供します.

  • Bundlerの目的は上記で説明しました.ではbunlderは何をみてプロジェクトで必要とされているgemをインストールするのでしょうか? 答えはGemfileとGemfile.lockです.

Gemfile

  • Gemfileはrailsアプリケーションに使用されているgemを記述したファイルです.具体的には以下のように記述します.
source 'https://rubygems.org'

gem 'rails', '3.2.1'

gem 'sqlite3'

gem 'json'

group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'

  gem 'uglifier', '>= 1.0.3'
end

sourceオプションにはgemをインストールするためのリポジトリを記述します. sourceオプションを指定することによって,gemをどこのリポジトリからインストールするかを指定します. また,例外的にあるgemだけインストールするリポジトリを変更したい場合はに以下のように明示的にsourceオプションを指定します.

source 'https://rubygems.org'
    gem "some_internal_gem", :source => "https://gems.example.com"
end

また,gemのバージョンを指定する場合はpessimistic operator(~>や>=のこと)を使用することが推奨されています.

github.com

pessimistic operator

  • pessimistic operatorを導入することでgemの管理を柔軟に対応することができます. まず,pessimistic operatorは若干癖があるので,簡単に説明します.
  gem 'sass-rails',   '~> 3.2.3'

と記述された場合,Gemfileは'sass-rails'は3.2.3以上3.3以下までのsass-railsのバージョンを使用するということをbundleに伝えます.

  gem 'sass-rails',   '~> 3.2.3.1'

と記述された場合,Gemfileは'sass-rails'は3.2.3.1以上3.2.3.2以下までのsass-railsのバージョンを使用するということをbundleに伝えます.

pessimistic operatorを指定する理由

  • 開発する立場からすると,updateに時間を割きたくありません.
gem "sass", "2.1.8"
gem 'fuga', "3.0.1"

のようにバージョンを指定して記述した場合を例に考えてみましょう. sass version 2.1.8に脆弱性が見つかり,version 2.1.9がリリースされたため,gemのupdateを行う必要があります.gemのバージョンをupdateするにはGemfileを

gem "sass", "2.1.9"
gem 'fuga', "3.0.1"

に書き換え

$ bundle update

を実行します. 次に,fugaに脆弱性が見つかり,3.0.2がリリースされました.そのためsassと同じようにGemfileを書き換え,updateコマンドを実行しました. 開発で使用しているgemが2つなら,人手でなんとか管理できるかもしれません.しかし,通常の開発では数多くのgemを使用して開発を行います.数多くのgemの中で,上記のような手順を踏んでupdateを行っていた場合,開発が全く進みません. pessimistic operatorを使用することで,上記のようなupdate作業に悩まされることはありません.

gem "sass", "~> 2.1.8"
gem 'fuga', "~> 3.0.1"

と記述することで

$ bundle update

コマンド1つでupdateを行うことができます.

ライブラリのupdateに時間をかけたくなければ,バージョンを指定しなければいいじゃないかと思う人もいるかもしれません. それはその通りです.

gem "sass"
gem 'fuga'

のように記述することで,Gemfileを書き換えず,updateコマンド1つでgemをupdateすることができます. しかし,gemのバージョンによっては記述方法が変わる場合があります. python2とpython3でプログラムの記述方式が変わったようにgemでもライブラリのバージョンが大きく変わった場合,そのようなことが起こりえます. 現在のソースコードをセキュリティ的に安全に使いたいやパフォーマンスをよくしたいが,gemのupdateによりソースコードを変更したくないという開発者のわがままな要求を実現できます.

Gemfile.lock

Gemfile.lockはインストールされたgemの特定のバージョンが記述されます. Gemfile.lockはGemfile.lockがないプロジェクト内で,bundle installを実行した場合に生成されます.

GEM
  remote: https://rubygems.org/
  specs:
    actionmailer (3.2.13)
      actionpack (= 3.2.13)
      mail (~> 2.5.3)

ネストはインストールしたgemの依存ライブラリを表しています.gemの開発に使用したgemなどがネストされ記述されています(多分). また,Gemfile.lockがあるプロジェクト内でbundle installを実行した場合は,Gemfile.lockを参照しgemのインストールを行います.

Gemfile.lockの必要性

Gemfile.lockはプロジェクト内で使用しているgemのバージョンを統一するために必要です. pessimistic operatorを使用して,gemのバージョンを指定した場合,bundle installを実行した時期によってインストールされるgemのバージョンが異なります. Gemfile.lockをプロジェクトのリポジトリに含めなければ,チーム内でばらばらのgemのバージョンを使用して作業することになりえます.

React/TypeScriptにおけるJSX element has no corresponding closing tagの解決法

.tsでジェネリクスを使用して,アロー関数の型宣言を行う方法

const hello = <T>(name: T) => {
    return name;
}

// hello<string>("igarashi");

これをそのままjsxに記述すると<T>がコンポーネントとして認識されてしまい,

TS17008: JSX element 'T' has no corresponding closing tag.

とアラートが表示される.

tsxジェネリクスを使用して,アロー関数の型宣言を行う方法

https://github.com/Microsoft/TypeScript/issues/15713 で議論されているように型宣言では以下のように記述する.

<P extends {}>or<P extends object>

上記のプログラムに反映すると

let hello = <T extends Object>(name: T) => {
    return name;
};

let hello = <T extends {}>(name: T) => {
    return name;
};

Optionに付与されたカスタムデータ属性の取得方法(React)

import React from "react";

const Option = ({ ops }) => {
  return (
    <React.Fragment>
      {ops.map((op, index) => {
        return (
          <option key={index} value={op.value} data-value={op["data-value"]}>
            {op.value}
          </option>
        );
      })}
    </React.Fragment>
  );
};

const Select = ({ children, onChange }) => {
  return <select onChange={onChange}>{children}</select>;
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
      custom_attribute: null
    };
  }

  onChange = e => {
    const customAttribute = e.target[e.target.selectedIndex].dataset.value;
    const value = e.target.value;
    this.setState({
      value: value,
      custom_attribute: customAttribute
    });
  };

  render() {
    const options = [
      { value: "one", "data-value": "1" },
      { value: "two", "data-value": "2" },
      { value: "three", "data-value": "3" }
    ];
    return (
      <React.Fragment>
        <Select onChange={this.onChange}>
          <Option ops={options} />
        </Select>
        <h1>{`value:${this.state.value}`}</h1>
        <h1>{`custom attribute:${this.state.custom_attribute}`}</h1>
      </React.Fragment>
    );
  }
}

export default App;

hookを使用した例

import React, { useState } from 'react';

const Option = ({ops}) => {
    return(
        <React.Fragment>
            {ops.map((op, index) => {
                return <option key={index} value={op.value} data-value={op['data-value']}>{op.value}</option>
            })}
        </React.Fragment>
    );
};

const Select = ({children, onChange}) => {
    return(
        <select onChange={onChange}>
            {children}
        </select>
    )
};

const App = () => {
    const [customAttribute, setCustomAttribute] = useState(null);
    const [value, setValue] = useState(null);
    const options = [
        {value: "one", 'data-value': '1'},
        {value: "two", 'data-value': '2'},
        {value: "three", 'data-value': '3'},
    ];
    const onChange = (e) => {
        const customAttribute = e.target[e.target.selectedIndex].dataset.value;
        const value = e.target.value;
        setValue(value);
        setCustomAttribute(customAttribute);
    };

    return(
        <div>
            <Select onChange={onChange}>
                <Option ops={options}/>
            </Select>
      <h1>{`value:${value}`}</h1>
      <h1>{`custom attribute:${customAttribute}`}</h1>
        </div>
    )
};

peeweeを使用したデータベースの操作 | pythonのORM

Peeweeについて

ORMフレームワークについて

ORMフレームワークについては以下の記事がわかりやすいです.

関係データベース(RDB)のレコードを、オブジェクトとして直感的に扱えるようにする。 また、RDBにアクセスするプログラムを書く際の煩雑な処理を軽減させ、 プログラマSQLを意識することなくプログラムを書ける。

https://qiita.com/yk-nakamura/items/acd071f16cda844579b9

model

from peewee import *
sqlite_db = SqliteDatabase('tutorial.sqlite3')


class BaseModel(Model):
    """A base model that will use our Sqlite database."""
    class Meta:
        database = sqlite_db


class Group(BaseModel):
    name = CharField()


class Member(BaseModel):
    name = CharField()
    group = ForeignKeyField(Group)

使い方

レコード保存

from peewee import *

sqlite_db = SqliteDatabase('tutorial.sqlite3')


class BaseModel(Model):
    """A base model that will use our Sqlite database."""

    class Meta:
        database = sqlite_db


class Group(BaseModel):
    name = CharField()


class Member(BaseModel):
    name = CharField()
    group = ForeignKeyField(Group)


sqlite_db.connect()
sqlite_db.create_tables([Member, Group])


with sqlite_db.atomic():
    for group_record in group_records:
        Group.create(name=group_record['name'])

# with sqlite_db.atomic():
#     for group_record in group_records:
#         Group.create(**group_record)

sqlite_db.close()

1対多(One-to-Many)のリレーションモデル

from peewee import *

sqlite_db = SqliteDatabase('tutorial.sqlite3')


class BaseModel(Model):
    """A base model that will use our Sqlite database."""

    class Meta:
        database = sqlite_db


class Group(BaseModel):
    name = CharField()


class Member(BaseModel):
    name = CharField()
    group = ForeignKeyField(Group, backref='members')


sqlite_db.connect()
sqlite_db.create_tables([Member, Group])

group_records = [{'name': "beatles"}, {"name": "bump"}]

with sqlite_db.atomic():
    for group_record in group_records:
        Group.create(name=group_record['name'])

beatles = Group.select().where(Group.name == 'beatles').first()
bump = Group.select().where(Group.name == 'bump').first()
member_records = [
    {'name': 'john', 'group_id': beatles},
    {'name': 'paul', 'group_id': beatles},
    {'name': 'fujiwara', 'group_id': bump},
    {'name': 'masukawa', 'group_id': bump}
]

with sqlite_db.atomic():
    for member_record in member_records:
        Member.create(**member_record)

# あるグループに所属しているメンバーが知りたい場合
group = Group.select().first()
for member in group.members:
    print(member.name)
# -----------------output-------------------
# john
# paul
# ------------------------------------------

# メンバーが所属しているグループが知りたい場合
member = Member.select().first()
for group in Group.select().where(Group.id == member):
    print(group.name)

# -----------------output-------------------
# beatles
# ------------------------------------------


# メンバーが所属しているグループが知りたい場合(INNER JOIN)
member = Member.select().first()
groups = Group.select().join(Member).where(Member.id == member.id)
for group in groups:
    print(group.name)
# -----------------output-------------------
# beatles
# ------------------------------------------

sqlite_db.close()

多対多(Many to Many)のリレーションモデル

from peewee import *

sqlite_db = SqliteDatabase('tutorial.sqlite3')


class BaseModel(Model):
    """A base model that will use our Sqlite database."""

    class Meta:
        database = sqlite_db


class Group(BaseModel):
    name = CharField()


class Member(BaseModel):
    name = CharField()
    group = ForeignKeyField(Group, backref='members')


class Office(BaseModel):
    name = CharField()


class OfficeGroup(BaseModel):
    group = ForeignKeyField(Group, backref='office_groups')
    office = ForeignKeyField(Office, backref='office_groups')


sqlite_db.connect()
sqlite_db.create_tables([Member, Group, Office, OfficeGroup])

group_records = [
    {'name': "beatles"},
    {"name": "bump"},
    {'name': 'amuro'}
]

with sqlite_db.atomic():
    for group_record in group_records:
        Group.create(name=group_record['name'])

beatles = Group.select().where(Group.name == 'beatles').first()
bump = Group.select().where(Group.name == 'bump').first()
member_records = [
    {'name': 'john', 'group_id': beatles},
    {'name': 'paul', 'group_id': beatles},
    {'name': 'fujiwara', 'group_id': bump},
    {'name': 'masukawa', 'group_id': bump}
]

with sqlite_db.atomic():
    for member_record in member_records:
        Member.create(**member_record)


office_records = [
    {'name': 'NEMS'},
    {'name': 'LONGFELLOW'},
    {'name': 'SONY'}
]

Office.insert_many(office_records).execute()


beatles = Group.select().where(Group.name == 'beatles').get()
bump = Group.select().where(Group.name == 'bump').get()

nems = Office.select().where(Office.name == 'NEMS').get()
long = Office.select().where(Office.name == 'LONGFELLOW').get()

office_group_records = [
    {'group_id': beatles, 'office_id': nems},
    {'group_id': beatles, 'office_id': long},
    {'group_id': bump, 'office_id': long},
]

OfficeGroup.insert_many(office_group_records).execute()


# beatlesが所属している事務所を知りたい場合
for office_group in beatles.office_groups:
    print(Office.select().where(Office.id == office_group.office_id).get().name)
# -----------------output-------------------
# NEMS
# LONGFELLOW
# ------------------------------------------

query = OfficeGroup.select(OfficeGroup, Group, Office).join(Group).switch(OfficeGroup).join(Office)

for i in query:
    print(i.group.name, i.office.name)

# -----------------output-------------------
# beatles NEMS
# beatles LONGFELLOW
# bump LONGFELLOW
# ------------------------------------------



sqlite_db.close()

コマンドラインでDynamodbに存在する特定のテーブルのレコードを全て取得する方法

ローカルでdynamodbを起動

$ java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb

dynamodbに存在する全てのテーブルを取得

  • ローカルで動かす場合は,--endpoint-urlオプションにhttp://localhost:port番号を記述する(忘れがち)
$ aws dynamodb list-tables --endpoint-url http://localhost:8000

Dynamodb特定のテーブルのレコードを全て取得する

$ aws dynamodb scan --table-name テーブル名 --endpoint-url http://localhost:8000