[Ruby] Learning record that made tweet function with STI pattern

3 minute read

The content of this article is pending review and may change!

What is STI (single table inheritance)?

Literally inherit one table from parent class to child class! In this case, the parent will be Tweet → the child will be Mediatweet and Texttweet. Classes for media tweet and text tweet respectively!

However, since the tables are the same, all classes, both parent and child, have the same columns. So Mediatweet also has a content column, and **Texttweet also has a img` column. **

db/schema.rb


  create_table "tweets", options: hogehoge
    t.text "content"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "img"
    t.integer "user_id"
    t.string "type"
  end

Why do you do that

It will be easier to write differences for model things. For example, if you write a blank invalidation validation only on a text tweet, the image can be tweet without text. However, since the contents are almost the same, I think that STI that can share the table is good.

Mounting method

  1. It was already in db/schema.rb, but the parent class (table) has a type column (string type).
  2. Make each model. Make the parent model with rails g model , etc. and make the child model etc. directly in app/model/

models/tweet.rb


class Tweet <ApplicationRecord
end

models/mediatweet.rb


class Mediatweet <Tweet
end

models/texttweet.rb


class Texttweet <Tweet
end

For now, this is the end of the STI implementation. The rest will be done automatically. For example, if you register the database with Mediatweet.create etc. with this, a record containing Mediatweet and data will be added to the type column.

Model distribution according to the contents of params acquired with form_with

I want to automatically sort the models depending on whether there is media data, so I implemented it as follows.

index.html.erb


<%= form_with model: @tweet, url: tweets_path do |form| %>
  <%= form.text_area :content %><br>
  <%= form.label "Upload image" %><br>
  <%= form.file_field :img %>
  <%= form.submit'post' %><br>
<% end %>

This time, it is a post and list page with the post form as it is in index. The image uploader is implemented with carriewave. I will omit about that. The parameters sent by submitting from the above form are

{
"authenticity_token"=>"hoge",
"tweet"=>{"content"=>"hoge", "img"=>"hoge"},
"commit"=>"Post"
}

You can get the parameters of this kind of double structure (structure with {} inside {}).

*By the way, If model: is specified in form_with, it becomes such a structure, and if only url: is specified, it does not become a double structure. I didn’t understand this and was really into it…

And it will automatically match the controller’s create action

tweets_controller.rb


  def create
    @tweet = tweettype_class.new(tweet_params)
    if @tweet.save
      redirect_to("/tweets")
    else
      @tweets = Tweet.all.order(created_at: :desc)
      render("index")
  # Return to the original post list page by rendering At this time, if you do not specify the url of form_with,
  The route of #form_with is pulled by the child model and it becomes an error (example) Original teets_path After rendering → text tweets_path becomes no much.
    end
  end
  private

  def tweet_params
  Only the part where the key is tweet is extracted by #require ({"content"=>"hoge","img"=>"hoge"})
    params.require(:tweet).permit(:content, :img).merge(user_id: @current_user.id)
  end
  Models are distributed based on the existence of #[:img] data
  #Since it has a double structure, the value cannot be taken correctly unless the key is specified double
  def tweettype
    case
    when params[:tweet][:img].present?
      'mediatweet'
    when params[:tweet][:img].blank?
      'text tweet'
    end
  end
  # Converts the given string to camel case and then to a constant name
  # (Example) mediatweet.cmelize=>Mediatweet.constantize=>Mediatweet(id: integer, content: text, created_at: datetime, hogehoge....)
  def tweettype_class
    tweettype.camelize.constantize
  end

With such a feeling, I tried to sort the models automatically depending on whether the image was uploaded.

This made it easier to write the roles for each model!