<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: hooopo wang</title>
    <description>The latest articles on DEV Community by hooopo wang (@hooopo).</description>
    <link>https://dev.to/hooopo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F142251%2Fc76fd1e1-e32d-49f8-a0e5-3e91bd4074a7.jpeg</url>
      <title>DEV Community: hooopo wang</title>
      <link>https://dev.to/hooopo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hooopo"/>
    <language>en</language>
    <item>
      <title>Build a Rails App with TiDB and the ActiveRecord TiDB Adapter</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Fri, 27 Aug 2021 08:48:17 +0000</pubDate>
      <link>https://dev.to/hooopo/build-a-rails-app-with-tidb-and-the-activerecord-tidb-adapter-23ga</link>
      <guid>https://dev.to/hooopo/build-a-rails-app-with-tidb-and-the-activerecord-tidb-adapter-23ga</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/pingcap/tidb"&gt;TiDB&lt;/a&gt; is an open-source NewSQL database that supports Hybrid Transactional and Analytical Processing (HTAP) workloads. It is MySQL compatible and features horizontal scalability, strong consistency, and high availability.&lt;/p&gt;

&lt;p&gt;I assumed using TiDB as a backend storage layer of Ruby on Rails application perhaps is a great way to manage storages into one place.&lt;/p&gt;

&lt;p&gt;This post describes how to get started and how to use TiDB as backend of Ruby on Rails applications for developers.&lt;/p&gt;

&lt;p&gt;Example source codes are available at &lt;a href="https://github.com/hooopo/rails-tidb"&gt;rails-tidb&lt;/a&gt; in GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up local TiDB server
&lt;/h2&gt;

&lt;p&gt;Install tiup&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; https://tiup-mirrors.pingcap.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting TiDB playground&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tiup playground  nightly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can connect to the TiDB instance just as connecting to MySQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 4000 &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initialize Ruby on Rails application
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
ruby 2.7.0

&lt;span class="nv"&gt;$ &lt;/span&gt;rails &lt;span class="nt"&gt;-v&lt;/span&gt;
Rails 6.1.4

&lt;span class="nv"&gt;$ &lt;/span&gt;rails new tidb-rails &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql &lt;span class="nt"&gt;--api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;a href="https://github.com/pingcap/activerecord-tidb-adapter"&gt;activerecord-tidb-adapter&lt;/a&gt; to Gemfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle add activerecord-tidb-adapter &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creating a new app, edit &lt;code&gt;config/database.yml&lt;/code&gt; to configure connection settings to TiDB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tidb&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utf8mb4&lt;/span&gt;
  &lt;span class="na"&gt;collation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utf8mb4_general_ci&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tidb_enable_noop_functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ON&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tidb_rails_development&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to add additional database configurations to use TiDB. It’s ready to use TiDB as a database of the Rails app!&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a database
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails db:create
Created database &lt;span class="s1"&gt;'tidb_rails_development'&lt;/span&gt;
Created database &lt;span class="s1"&gt;'tidb_rails_test'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Manipulate TiDB data through Rails app
&lt;/h2&gt;

&lt;p&gt;Defining Model using rails g command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails g model user email:string name:string gender:integer
...
&lt;span class="nv"&gt;$ &lt;/span&gt;vim ./db/migrate/20210826174523_create_users.rb &lt;span class="c"&gt;# edit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;db/migrate/20210826174523_create_users.rb&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateUsers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:gender&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, apply database migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rails db:migrate
== 20210826174523 CreateUsers: migrating ======================================
-- create_table(:users)
   -&amp;gt; 0.1717s
== 20210826174523 CreateUsers: migrated (0.1717s) =============================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launch Rails console to play with the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails c
Running via Spring preloader &lt;span class="k"&gt;in &lt;/span&gt;process 13378
Loading development environment &lt;span class="o"&gt;(&lt;/span&gt;Rails 6.1.4.1&lt;span class="o"&gt;)&lt;/span&gt;
irb&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;:001:0&amp;gt; 30.times.each &lt;span class="o"&gt;{&lt;/span&gt; |i| User.create!&lt;span class="o"&gt;(&lt;/span&gt;email: &lt;span class="s2"&gt;"user-#{i}@example.com"&lt;/span&gt;, name: &lt;span class="s2"&gt;"user-#{i}"&lt;/span&gt;, gender: i % 3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
   &lt;span class="o"&gt;(&lt;/span&gt;1.2ms&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;select &lt;/span&gt;version&lt;span class="o"&gt;()&lt;/span&gt;
  TRANSACTION &lt;span class="o"&gt;(&lt;/span&gt;0.8ms&lt;span class="o"&gt;)&lt;/span&gt;  BEGIN
  User Create &lt;span class="o"&gt;(&lt;/span&gt;93.5ms&lt;span class="o"&gt;)&lt;/span&gt;  INSERT INTO &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;email&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;name&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;gender&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;created_at&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;updated_at&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; VALUES &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user-0@example.com'&lt;/span&gt;, &lt;span class="s1"&gt;'user-0'&lt;/span&gt;, 0, &lt;span class="s1"&gt;'2021-08-26 17:50:40.661945'&lt;/span&gt;, &lt;span class="s1"&gt;'2021-08-26 17:50:40.661945'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  TRANSACTION &lt;span class="o"&gt;(&lt;/span&gt;14.9ms&lt;span class="o"&gt;)&lt;/span&gt;  COMMIT
...
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 30
irb&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;:002:0&amp;gt; User.count
   &lt;span class="o"&gt;(&lt;/span&gt;8.9ms&lt;span class="o"&gt;)&lt;/span&gt;  SELECT COUNT&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; FROM &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 30
irb&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;:003:0&amp;gt; User.first
  User Load &lt;span class="o"&gt;(&lt;/span&gt;5.8ms&lt;span class="o"&gt;)&lt;/span&gt;  SELECT &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt; FROM &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; ORDER BY &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;.&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; ASC LIMIT 1
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#&amp;lt;User id: 1, email: "user-0@example.com", name: "user-0", gender: 0, created_at: "2021-08-26 17:50:40.661945000 +0000", updated_at: "2021-08-26 17:50:40.661945000 +0000"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;TiDB offers MySQL interfaces which can be used as backend database layers of Ruby on Rails applications. &lt;/p&gt;

&lt;p&gt;We can use ActiveRecord ORM directly, or use activerecord-tidb-adpater, a lightweight extension of ActiveRecord that supports several rails versions, including 5.2, 6.1, and 7.0. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/pingcap/activerecord-tidb-adapter"&gt;activerecord-tidb-adapter&lt;/a&gt; provides compatible patches and some tidb-specific functions, such as &lt;a href="https://docs.pingcap.com/tidb/stable/sql-statement-create-sequence"&gt;Sequence&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tidb</category>
      <category>activerecord</category>
    </item>
    <item>
      <title>Getting Started with Rails &amp; TiDB</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Wed, 21 Apr 2021 17:54:57 +0000</pubDate>
      <link>https://dev.to/hooopo/getting-started-with-rails-tidb-4pni</link>
      <guid>https://dev.to/hooopo/getting-started-with-rails-tidb-4pni</guid>
      <description>&lt;h1&gt;
  
  
  Getting Started with Rails &amp;amp; TiDB
&lt;/h1&gt;

&lt;p&gt;Perhaps the first Rails + TiDB integration tutorial, there are so few articles on the web for newbies to get started, and there is a real barrier to integrating a complex ORM like ActiveRecord with TiDB, so I wrote this tutorial to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a local TiDB development environment
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Installing TiUP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The TiUP installation process is very straightforward for both Darwin and Linux operating systems, and can be successfully installed with a single command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; https://tiup-mirrors.pingcap.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Local start cluster&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiup playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tiup playground
Starting component ``playground``: /Users/hooopo/.tiup/components/playground/v1.4.1/tiup-playground
Use the latest stable version: v5.0.0

    Specify version manually: tiup playground &amp;lt;version&amp;gt;
    The stable version: tiup playground v4.0.0
    The nightly version: tiup playground nightly

Playground Bootstrapping...
Start pd instance
Start tikv instance
Start tidb instance
Waiting for tidb instances ready
127.0.0.1:4000 ... Done
Start tiflash instance
Waiting for tiflash instances ready
127.0.0.1:3930 ... Done
CLUSTER START SUCCESSFULLY, Enjoy it ^-^
To connect TiDB: mysql --host 127.0.0.1 --port 4000 -u root -p (no password)
To view the dashboard: http://127.0.0.1:2379/dashboard
To view the Prometheus: http://127.0.0.1:9090
To view the Grafana: http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl.ruby-china.com%2Fphoto%2Fhooopo%2F3d24d840-45ec-420e-9f84-f8e48989e73c.png%21large" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl.ruby-china.com%2Fphoto%2Fhooopo%2F3d24d840-45ec-420e-9f84-f8e48989e73c.png%21large"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;detailed documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pingcap.com/tidb/stable/tiup-overview" rel="noopener noreferrer"&gt;https://docs.pingcap.com/tidb/stable/tiup-overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rails &amp;amp; TiDB Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create a Rails project&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new myapp --database=mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;database.yml configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql2&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utf8mb4&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tidb_enable_noop_functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the default port for the local cluster started by tidup is 4000, set the database connection variable tidb_enable_noop_functions: ON because Rails requires the get_lock function, which is turned off by default in tidb.&lt;/p&gt;

&lt;p&gt;If you configure the database link using the URI method, it is similar to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql2&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utf8mb4&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("DB_URL") || "mysql2://root:pass@localhost:4000/myapp" %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tidb_enable_noop_functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;primary-key, auto-increment, unique-index&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a users table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateUsers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:username&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a unique index&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddUniqueIndexForEmail&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no difference to using standalone MySQL, TiDB is already very compatible and much easier to get started with than other distributed databases, some of which are incompatible with primary keys, self-incrementing, unique indexes and other features that require additional handling.&lt;/p&gt;

&lt;p&gt;Take a look at the generated data table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; show create table users;
+ -------+------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------+
| users | CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `username` varchar(255) DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
  UNIQUE KEY `index_users_on_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001 |
+-------+------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------+
1 row in set (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;savepoint patch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The only obstacle to combining TiDB and ActiveRecord is that TiDB does not support savepoint, I wrote a simple patch to solve it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://github.com/rails/rails/blob/6-1-stable/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L313&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_record/connection_adapters/abstract/database_statements.rb'&lt;/span&gt;


&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;DisableSavepoint&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;requires_new: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;isolation: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;joinable: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;requires_new&lt;/span&gt;
      &lt;span class="n"&gt;requires_new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt; &lt;span class="s2"&gt;"savepoint statement was used, but your db not support, ignored savepoint."&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt; &lt;span class="nb"&gt;caller&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;requires_new: &lt;/span&gt;&lt;span class="n"&gt;requires_new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;isolation: &lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;joinable: &lt;/span&gt;&lt;span class="n"&gt;joinable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;requires_new: &lt;/span&gt;&lt;span class="n"&gt;requires_new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;isolation: &lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;joinable: &lt;/span&gt;&lt;span class="n"&gt;joinable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DatabaseStatements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:prepend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DisableSavepoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The principle is that Rails will only introduce savepoint when transaction passes the parameter requires_new to true, and then output logs to migrate through the patch where requires_new is true to nil. My experience is that most Rails projects don't use savepoint much, so it's not hard to migrate if you want to. When running migration, savepoint is introduced, but in scenarios where there is no concurrent migration, it's not a big deal to remove it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pingcap.com/tidb/dev/release-5.0.0" rel="noopener noreferrer"&gt;What's New in TiDB 5.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pingcap.com/tidb/stable/system-variables#tidb_enable_noop_functions-new-in-v40" rel="noopener noreferrer"&gt;tidb_enable_noop_functions-new-in-v40
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pingcap.com/tidb/stable/mysql-compatibility" rel="noopener noreferrer"&gt;Compatibility with MySQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hooopo/rails-tidb" rel="noopener noreferrer"&gt;rails-tidb demo full source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tidb</category>
      <category>mysql</category>
      <category>rails</category>
      <category>activerecord</category>
    </item>
    <item>
      <title>Problems with google analytics</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Fri, 05 Mar 2021 21:15:49 +0000</pubDate>
      <link>https://dev.to/hooopo/problems-with-google-analytics-39l5</link>
      <guid>https://dev.to/hooopo/problems-with-google-analytics-39l5</guid>
      <description>&lt;p&gt;Google analytics is a great free behavioural analysis tool that is perfectly suited to the needs of small and medium sized businesses, and is even used by some unicorn level companies.&lt;/p&gt;

&lt;p&gt;However, Google analytics is not perfect and when you want to use GA data in depth, you will find many limitations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GA's API does not provide raw data at event level or hit level, only aggregated data; this can be solved by purchasing GA360 and bigquery solutions, but it costs at least $15w per year&lt;/li&gt;
&lt;li&gt;GA data has retention and sampling limits, as per rules: &lt;a href="https://support.google.com/analytics/answer/7667196?hl=zh-Hans"&gt;https://support.google.com/analytics/answer/7667196?hl=zh-Hans&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Free means your data is used to train Google's ad serving&lt;/li&gt;
&lt;li&gt;dimension and metrics limits, probably API limits of 7 dimensions and 10 metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, it means that you don't own the data collected by GA, you can only consume the reports. Sometimes we need to use behavioural data in depth, for example in scenarios such as the following.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customised reporting logic&lt;/li&gt;
&lt;li&gt;Integration with existing data platforms, such as taking behavioural data and using it for recommendation systems and search systems&lt;/li&gt;
&lt;li&gt;Developing a CRM system based on behavioural data, such as sending SMS messages to users who have clicked on a product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have recently been planning to develop an alternative to Google Analytics to try and solve the problems mentioned above.&lt;/p&gt;

&lt;p&gt;The shape of the product is basically established, directly using the GA4 sdk and then configuring the transport_url to achieve the replacement, providing a simple visualisation + custom queries, or docking metabase yourself.&lt;/p&gt;

&lt;p&gt;Open source and SaaS versions are available.&lt;/p&gt;

&lt;p&gt;Compared to the advantages of local deployment, data is permanently stored in their own servers, do not need to be subject to the GA retention time and sampling rules, and provides the original data, GA, if you get the original data you need to use a paid service, GA360 such, $150k per year, and export to bigquery is also a paid service.&lt;/p&gt;

&lt;p&gt;Advantages over other open source Google Analytics alternatives.&lt;/p&gt;

&lt;p&gt;Research the existing open source products, such as plausible and umani, are more than 5k star open source projects; are only to provide the basic model of the function, these and GA than still very much worse; such as e-commerce analysis, custom events, multiple subsites, custom dimensions and metrics and other features are not provided.&lt;/p&gt;

</description>
      <category>google</category>
      <category>analytics</category>
    </item>
    <item>
      <title>I wrote an open source alternative to google analytics using timescaledb and rails and opentresty</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Fri, 05 Mar 2021 21:13:01 +0000</pubDate>
      <link>https://dev.to/hooopo/i-wrote-an-open-source-alternative-to-google-analytics-using-timescaledb-and-rails-and-opentresty-55cp</link>
      <guid>https://dev.to/hooopo/i-wrote-an-open-source-alternative-to-google-analytics-using-timescaledb-and-rails-and-opentresty-55cp</guid>
      <description>&lt;p&gt;Last time I shared some &lt;a href="https://dev.to/hooopo/problems-with-google-analytics-39l5"&gt;thoughts on Google Analytics&lt;/a&gt;, I planned to implement a Google Analytics that can be deployed privately, and after months of trying, I finally finished writing the prototype. It's only the simplest of features at the moment, with some killer features to be added later...&lt;/p&gt;

&lt;p&gt;Hypercable Analytics is a fully featured high performance scalable, open source, standalone deployable alternative to Google Analytics, build with timescaledb openresty redis and rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  screenshot
&lt;/h2&gt;

&lt;p&gt;screenshot &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl.ruby-china.com%2Fphoto%2Fhooopo%2F4da95824-f7d6-4a30-a91b-aa1db73186e3.png%21large" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fl.ruby-china.com%2Fphoto%2Fhooopo%2F4da95824-f7d6-4a30-a91b-aa1db73186e3.png%21large"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  demo site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learnsql.io" rel="noopener noreferrer"&gt;https://learnsql.io&lt;/a&gt; (Note: demo project, data will be cleared later, email: &lt;a href="mailto:hoooopo@gmail.com"&gt;hoooopo@gmail.com&lt;/a&gt; password: 111111) &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hackershare.dev" rel="noopener noreferrer"&gt;https://hackershare.dev&lt;/a&gt; (site with hypercable analytics tracker installed)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/HyperCable/hypercable" rel="noopener noreferrer"&gt;https://github.com/HyperCable/hypercable&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  benchmark
&lt;/h2&gt;

&lt;p&gt;Tested with a 6C cloud server, can handle about 15k rps, can do some more optimizations and compare with open source competitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;More built-in visual reports&lt;/li&gt;
&lt;li&gt;Built-in metabase-like interface and SQL custom exploration&lt;/li&gt;
&lt;li&gt;UI for e-commerce module (data structures are currently supported)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>timescaledb</category>
      <category>redis</category>
      <category>postgres</category>
      <category>rails</category>
    </item>
    <item>
      <title>hackershare: Social bookmarking reinvented!</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Thu, 24 Sep 2020 08:27:54 +0000</pubDate>
      <link>https://dev.to/hooopo/hackershare-social-bookmarking-reinvented-3170</link>
      <guid>https://dev.to/hooopo/hackershare-social-bookmarking-reinvented-3170</guid>
      <description>&lt;p&gt;Hackershare is a bookmark sharing platform, you can share the webpage you are browsing with one click through chrome extension. Different from bookmark management tools such as pocket, hackershare encourages sharing your bookmarks instead of keeping them privately.&lt;/p&gt;

&lt;p&gt;Popular tags are a must-have feature of the bookmark tool, which can facilitate the management of your bookmarks. To become a powerful information organization tool is the goal of hackershare. In the future, many optimizations will be made on the label, such as synonym ring, preferred term, upper term, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqrya4yqg9vthtkzfvd7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqrya4yqg9vthtkzfvd7a.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hackershare is a discovery platform. The system sorts the content by users' clicks, browses, favorites, comments, etc., and calculates the daily, weekly, monthly, and total popularity rankings. At present, the content of hackershare is mainly for programming development, product operation, UI design, entrepreneurial thinking, etc.&lt;/p&gt;

&lt;p&gt;Hackershare is an RSS subscription platform; the system will regularly crawl technology-related popular RSS feeds. You only need to subscribe to receive notifications of RSS updates. The effect is the same as an RSS reader. At the same time, you can also subscribe to the content you are interested in according to other dimensions, such as following users and following tags, so that users and tags-related content will be pushed to you, and you can master technical consultation with one click.&lt;/p&gt;

&lt;p&gt;In the future, users will be supported to submit their own RSS feeds, which you can use to promote your blog and products.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F21gkdbq2rjnybikzno5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F21gkdbq2rjnybikzno5p.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hackershare is an open source project with a wide range of applications. For example, if it is used to put product links, it can become a diversion station worth buying;&lt;/p&gt;

&lt;p&gt;If used for news consultation, it can become a news station;&lt;/p&gt;

&lt;p&gt;If the information is shared within the team, it can become an internal knowledge base.&lt;/p&gt;

&lt;p&gt;Compared with ordinary forums or CMS, hackershare is lighter, SEO friendly, has better information architecture, and automation features. If you build some diversion stations, you can even save the time of writing crawlers.&lt;/p&gt;

&lt;p&gt;Project address: &lt;a href="https://hackershare.dev" rel="noopener noreferrer"&gt;https://hackershare.dev&lt;/a&gt;&lt;br&gt;
github：&lt;a href="https://github.com/hooopo/hackershare" rel="noopener noreferrer"&gt;https://github.com/hooopo/hackershare&lt;/a&gt;&lt;br&gt;
chrome extension：&lt;a href="https://github.com/hooopo/hackershare-ext" rel="noopener noreferrer"&gt;https://github.com/hooopo/hackershare-ext&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>rss</category>
      <category>sharing</category>
      <category>hackernews</category>
    </item>
    <item>
      <title>Online DB Doc tool</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Sun, 21 Jun 2020 12:57:44 +0000</pubDate>
      <link>https://dev.to/hooopo/online-db-doc-tool-c9l</link>
      <guid>https://dev.to/hooopo/online-db-doc-tool-c9l</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PuBDLXuz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l.ruby-china.com/photo/2020/a77f3aaa-3686-43b0-9d1c-4d581d2e4a25.png%2521large" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PuBDLXuz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l.ruby-china.com/photo/2020/a77f3aaa-3686-43b0-9d1c-4d581d2e4a25.png%2521large" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DrawERD add a new function to generate db doc for your database structures. &lt;a href="https://drawerd.com"&gt;https://drawerd.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LlQ5fxtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l.ruby-china.com/photo/2020/fefeafc4-32f1-4db9-a649-2951f207ffaa.png%2521large" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LlQ5fxtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://l.ruby-china.com/photo/2020/fefeafc4-32f1-4db9-a649-2951f207ffaa.png%2521large" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>db</category>
      <category>erd</category>
      <category>doc</category>
    </item>
    <item>
      <title>Free database structure tool online</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Sun, 26 Apr 2020 08:46:54 +0000</pubDate>
      <link>https://dev.to/hooopo/free-database-structure-tool-online-3llk</link>
      <guid>https://dev.to/hooopo/free-database-structure-tool-online-3llk</guid>
      <description>&lt;h2&gt;
  
  
  Why ERD
&lt;/h2&gt;

&lt;p&gt;The database model is the core of your application, describing data tables, data types, entity relationships and constraints, and is the most important means of communication during the project development phase. A clear ERD can make it easier for the team to understand the needs and grasp the overall picture of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Startup projects&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For start-up projects or new requirements, being able to correctly establish a data model that meets business needs is a key factor in the project's smooth iteration. With tools like &lt;a href="https://drawerd.com/"&gt;DrawERD&lt;/a&gt;, you can quickly transform your requirements into visual ERDs, and reach consensus among team members. There is no need to repeatedly check whether "category and product are one-to-many or many-to-many?" Information that is ignored but particularly critical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legacy projects&lt;/strong&gt;&lt;br&gt;
For legacy projects, the general business has been very stable, but the newcomer has just joined the team, and the business system in the face of hundreds of tables is often unintelligible. With DrawERD, newcomers can quickly understand project data relationships and have a systematic understanding of applications. If your database already has hundreds of tables and you plan to migrate from a monolithic application to a MicroService, then DrawERD's grouping function is the best tool. By simulating the grouping of modules, you can clearly determine which entity is placed in which service More reasonable.&lt;br&gt;
Why not alternative?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7LB0xRJ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c40r13cs15a2ij8imbya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7LB0xRJ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c40r13cs15a2ij8imbya.png" alt="Alt Text" width="715" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The picture above is the core feature of DrawERD. Compared with the popular modeling tools on the market, it does a lot of tradeoff. Let me talk about the reasons for each decision.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS vs desktop tool：Compared to desktop tools, team collaboration is a goal of DrawERD. You can generate urls and embed them in your project management tools, and changes in ERD will be automatically synchronized.&lt;/li&gt;
&lt;li&gt;Auto layout vs Drawing on canvas manually：Many tools edit ERD based on drag and drop on the canvas. This way looks cool, but when it is actually used, if your application reaches dozens of tables, it is a disaster. DrawERD uses automatic layout, which will automatically render a fresh and beautiful SVG image according to your entities and relationships. At the same time, you can choose a combination of mode and layout for rendering.&lt;/li&gt;
&lt;li&gt;Database agnostic vs Database binding：Some tools need to rely on the database connection to reverse the data structure. DrawERD chooses to use static analysis. You only need to export the CSV file from the information_schema of your existing database to upload. For new projects, you only need to create entities and relationships on the interface. Rely on any foreign key and meta-information of the database. At the same time, for the rails project, DrawERD integrates the Rails ERD gem, you can seamlessly migrate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Full Mode&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sRPMco7i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f1zsfkbtf210itmzmsav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sRPMco7i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f1zsfkbtf210itmzmsav.png" alt="Alt Text" width="880" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple Mode&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--38D0m1CF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kwzurwpv723z6ima1nq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--38D0m1CF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kwzurwpv723z6ima1nq8.png" alt="Alt Text" width="880" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grouping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fHHdVink--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/df7i913urb9zy8bthq3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fHHdVink--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/df7i913urb9zy8bthq3g.png" alt="Alt Text" width="880" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full video here: &lt;a href="https://www.loom.com/share/e30d06ba299b43bc8b68f369b47f745a"&gt;https://www.loom.com/share/e30d06ba299b43bc8b68f369b47f745a&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Try DrawERD online: &lt;a href="https://drawerd.com"&gt;https://drawerd.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>rails</category>
      <category>python</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Petri Net Workflow engine for Rails</title>
      <dc:creator>hooopo wang</dc:creator>
      <pubDate>Sat, 08 Feb 2020 19:46:02 +0000</pubDate>
      <link>https://dev.to/hooopo/petri-net-workflow-engine-for-rails-2jgn</link>
      <guid>https://dev.to/hooopo/petri-net-workflow-engine-for-rails-2jgn</guid>
      <description>&lt;h1&gt;
  
  
  Petri Flow
&lt;/h1&gt;

&lt;p&gt;Workflow engine for Rails: &lt;a href="https://github.com/hooopo/petri_flow"&gt;https://github.com/hooopo/petri_flow&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full petri net features support (seq, parallel, iterative, timed, automitic etc.)&lt;/li&gt;
&lt;li&gt;Both approval workflow and business workflow.&lt;/li&gt;
&lt;li&gt;Simple web admin for workflow definition and case management.&lt;/li&gt;
&lt;li&gt;Build-in simple dynamic form.&lt;/li&gt;
&lt;li&gt;Replaceable dynamic form.&lt;/li&gt;
&lt;li&gt;Graph screen for workflow definition.&lt;/li&gt;
&lt;li&gt;Graph screen for case and token migration.&lt;/li&gt;
&lt;li&gt;Powerful guard expression.&lt;/li&gt;
&lt;li&gt;MySQL and Postgres Support.&lt;/li&gt;
&lt;li&gt;Powerful assignment management.&lt;/li&gt;
&lt;li&gt;Flexible integration of organizational structure system(role, group, position or department etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Docs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hooopo.gitbook.io/petri-flow/"&gt;Petri-Nets and Workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hooopo.gitbook.io/petri-flow/workflow-conceptual-guide"&gt;Workflow Conceptual Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hooopo.gitbook.io/petri-flow/workflow-concepts-reference"&gt;Workflow Concepts Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hooopo.gitbook.io/petri-flow/erd"&gt;Petri Flow ERD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hooopo.gitbook.io/petri-flow/developer-document"&gt;Developer Doc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;h3&gt;
  
  
  iterative routing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o15Vz5pf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAAQJbxDdaxoaYVda%252Fimage.png%3Falt%3Dmedia%26token%3De74d1ae7-fa16-47ab-83b5-ad73a382fa07" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o15Vz5pf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAAQJbxDdaxoaYVda%252Fimage.png%3Falt%3Dmedia%26token%3De74d1ae7-fa16-47ab-83b5-ad73a382fa07" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  parallel_routing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GM34Fjis--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAKm9VN1MJxPZT9Xe%252Fimage.png%3Falt%3Dmedia%26token%3Dc8beba84-72ec-470f-9987-81cf40762e15" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GM34Fjis--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAKm9VN1MJxPZT9Xe%252Fimage.png%3Falt%3Dmedia%26token%3Dc8beba84-72ec-470f-9987-81cf40762e15" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  guard
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3ruIZsQ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAT8Ui_xjqy9Niccp%252Fimage.png%3Falt%3Dmedia%26token%3Dde4298fb-14b9-40bc-ab75-92ef0b98a533" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3ruIZsQ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAT8Ui_xjqy9Niccp%252Fimage.png%3Falt%3Dmedia%26token%3Dde4298fb-14b9-40bc-ab75-92ef0b98a533" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  case state graph
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z0Gu3QBK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAeeR42ZRVIVKuUae%252Fimage.png%3Falt%3Dmedia%26token%3D90c96af9-d01f-4d6e-ae2b-445ea343a5ac" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z0Gu3QBK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%252F-M-GhlU_QaD6nbLAbaJI%252F-M-X0nIxUUBwJsNhY4FN%252F-M-XAeeR42ZRVIVKuUae%252Fimage.png%3Falt%3Dmedia%26token%3D90c96af9-d01f-4d6e-ae2b-445ea343a5ac" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Add this line to your application's Gemfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'petri_flow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="s1"&gt;'wf'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And then execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Install graphviz&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install graphviz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rake wf:install:migrations
bundle exec rails db:create
bundle exec rails db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Add wf_config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/wf_config.rb&lt;/span&gt;
&lt;span class="no"&gt;Wf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_class&lt;/span&gt;
    &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# you can add more org class, for example, Role, Department, Position etc.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;org_classes&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;group: &lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Group&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Set parties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;optional: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:party&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :partable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Wf::Party'&lt;/span&gt;

  &lt;span class="c1"&gt;# NOTICE: group or user or role all has_many users&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: :id&lt;/span&gt;

  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_party&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;party_name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Group&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:party&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :partable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Wf::Party'&lt;/span&gt;
  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_party&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;party_name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rails 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000/wf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Demo App: &lt;a href="https://github.com/hooopo/petri_flow_demo"&gt;https://github.com/hooopo/petri_flow_demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Contribution directions go here.&lt;/p&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;The gem is available as open source under the terms of the &lt;a href="https://opensource.org/licenses/MIT"&gt;MIT License&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>workflow</category>
      <category>petrinet</category>
    </item>
  </channel>
</rss>
