Rails Coding Conventions (v1) - Use Ruby Style on Rails.
White

pdkproitf viết ngày 20/06/2017

This article will discuss about the basic styles are used common on Rails include: How to use Ruby convention on Rails and Rails convention.
Why convention?
Let take a look at this example: You are a developer at a company. A nice day, your boss asks you to fix a bug on an old project. Unfortunately, the main developer worked on that project leaved the company and you have to read through his code. This time you'll find out convention code is so great.
Real-world Rails programmers can write code that can be maintained by other real-world Rails programmers. A style guide that reflects real-world usage gets used, and a style guide that holds to an ideal that has been rejected by the people it is supposed to help risks not getting used at all – no matter how good it is.

Use Ruby Style on Rails.

  • Comments
  • White Space.
  • Naming
  • Class Organization
  • Code
  • Method

Comments

Though a pain to write, comments are absolutely vital to keeping our code readable. The following rules describe what you should comment and where. But remember: while comments are very important, the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments.
When writing your comments, write for your audience: the next contributor who will need to understand your code. Be generous — the next one may be you!

  • Write self-documenting code and ignore the rest of this section. Seriously!
  • Write comments in English.
  • Use one space between the leading # character of the comment and the text of the comment.
  • Comments longer than a word are capitalized and use punctuation. Use one space after periods.
  • Avoid superfluous comments.
# bad
counter += 1 # Increments counter by one.
  • Keep existing comments up-to-date. An outdated comment is worse than no comment at all.

    Good code is like a good joke: it needs no explanation.
    — old programmers maxim, through Russ Olsen

  • Avoid writing comments to explain bad code. Refactor the code to make it self-explanatory. ("Do or do not—there is no try." Yoda).

File/Classes & Modules

Every class definition should have an accompanying comment that describes what it is for and how it should be used.

  • A file that contains zero classes or more than one class should have a comment at the top describing its contents.
# Automatic conversion of one locale to another where it is possible, like
# American to British English.
module Translation
  # Class for converting between text between similar locales.
  # Right now only conversion between American English -> British, Canadian,
  # Australian, New Zealand variations is provided.
  class PrimAndProper
    def initialize
      @converters = { :en => { :"en-AU" => AmericanToAustralian.new,
                               :"en-CA" => AmericanToCanadian.new,
                               :"en-GB" => AmericanToBritish.new,
                               :"en-NZ" => AmericanToKiwi.new,
                             } }
    end

  ...

  # Applies transforms to American English that are common to
  # variants of all other English colonies.
  class AmericanToColonial
    ...
  end

  # Converts American to British English.
  # In addition to general Colonial English variations, changes "apartment"
  # to "flat".
  class AmericanToBritish < AmericanToColonial
    ...
  end

Function comments

Every function declaration should have comments immediately preceding it that describe what the function does and how to use it. These comments should be descriptive ("Opens the file") rather than imperative ("Open the file"); the comment describes the function, it does not tell the function what to do. In general, these comments do not describe how the function performs its task. Instead, that should be left to comments interspersed in the function's code.

Every function should mention what the inputs and outputs are, unless it meets all of the following criteria:

  • not externally visible
  • very short
  • obvious

You may use whatever format you wish. In Ruby, two popular function documentation schemes are TomDoc and YARD. You can also just write things out concisely:

# Returns the fallback locales for the_locale.
# If opts[:exclude_default] is set, the default locale, which is otherwise
# always the last one in the returned list, will be excluded.
#
# For example:
#   fallbacks_for(:"pt-BR")
#     => [:"pt-BR", :pt, :en]
#   fallbacks_for(:"pt-BR", :exclude_default => true)
#     => [:"pt-BR", :pt]
def fallbacks_for(the_locale, opts = {})
  ...
end

Comment Annotations

  • Annotations should usually be written on the line immediately above the relevant code.
  • The annotation keyword is followed by a colon and a space, then a note describing the problem.
  • If multiple lines are required to describe the problem, subsequent lines should be indented three spaces after the # (one general plus two for indentation purpose).
def bar
  # FIXME: This has crashed occasionally since v3.2.1. It may
  #   be related to the BarBazUtil upgrade.
  baz(:quux)
end
  • In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule.
def bar
  sleep 100 # OPTIMIZE
end
  • Use TODO to note missing features or functionality that should be added at a later date.
  • Use FIXME to note broken code that needs to be fixed.
  • Use OPTIMIZE to note slow or inefficient code that may cause performance problems.
  • Use HACK to note code smells where questionable coding practices were used and should be refactored away.
  • Use REVIEW to note anything that should be looked at to confirm it is working as intended. For example: REVIEW: Are we sure this is how the client does X currently?.
  • Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project's README or similar.

Magic Comments

  • Place magic comments above all code and documentation. Magic comments should only go below shebangs if they are needed in your source file.
# good
# frozen_string_literal: true
# Some documentation about Person
class Person
end

# bad
# Some documentation about Person
# frozen_string_literal: true
class Person
end
# good
#!/usr/bin/env ruby
# frozen_string_literal: true
App.parse(ARGV)

# bad
# frozen_string_literal: true
#!/usr/bin/env ruby
App.parse(ARGV)
  • Use one magic comment per line if you need multiple.
# good
# frozen_string_literal: true
# encoding: ascii-8bit

# bad
# -*- frozen_string_literal: true; encoding: ascii-8bit -*-
  • Separate magic comments from code and documentation with a blank line.
# good
# frozen_string_literal: true

# Some documentation for Person
class Person
  # Some code
end

# bad
# frozen_string_literal: true
# Some documentation for Person
class Person
  # Some code
end

Block and inline, Header/Footer comments

The final place to have comments is in tricky parts of the code. If you're going to have to explain it at the next code review, you should comment it now. Complicated operations get a few lines of comments before the operations commence. Non-obvious ones get comments at the end of the line.

def fallbacks_for(the_locale, opts = {})
  # dup() to produce an array that we can mutate.
  ret = @fallbacks[the_locale].dup

  # We make two assumptions here:
  # 1) There is only one default locale (that is, it has no less-specific
  #    children).
  # 2) The default locale is just a language. (Like :en, and not :"en-US".)
  if opts[:exclude_default] &&
      ret.last == default_locale &&
      ret.last != language_from_locale(the_locale)
    ret.pop
  end

  ret
end

On the other hand, never describe the code. Assume the person reading the code knows the language (though not what you're trying to do) better than you do.

Related: do not use block comments. They cannot be preceded by whitespace and are not as easy to spot as regular comments.

# bad
=begin
comment line
another comment line
=end

# good
# comment line
# another comment line
  • Block Comments
# Here is block comment.

#

# foo, bar, buz.
  • Single-Line Comments
# This is single line comment.
  • Tailing Comments
if a == 2

true # special case

else

prime?(a) # work only for odd a

end
  • Header/Footer
=begin

= FooBar library

== What's New?

.....

....

== Installation

.....

....

....

=end

Punctuation, spelling and grammar

Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.

Comments should be as readable as narrative text, with proper capitalization and punctuation. In many cases, complete sentences are more readable than sentence fragments. Shorter comments, such as comments at the end of a line of code, can sometimes be less formal, but you should be consistent with your style.

Although it can be frustrating to have a code reviewer point out that you are using a comma when you should be using a semicolon, it is very important that source code maintain a high level of clarity and readability. Proper punctuation, spelling, and grammar help with that goal.

TODO comments

Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.

TODOs should include the string TODO in all caps, followed by the full name of the person who can best provide context about the problem referenced by the TODO, in parentheses. A colon is optional. A comment explaining what there is to do is required. The main purpose is to have a consistent TODO format that can be searched to find the person who can provide more details upon request. A TODO is not a commitment that the person referenced will fix the problem. Thus when you create a TODO, it is almost always your name that is given.

  # bad
  # TODO(RS): Use proper namespacing for this constant.

  # bad
  # TODO(drumm3rz4lyfe): Use proper namespacing for this constant.

  # good
  # TODO(Ringo Starr): Use proper namespacing for this constant.

Commented-out code

Never leave commented-out code in our codebase.

Way of no commenting

  • If you can write simple, short and light scripts, comments may not be necessary.

  • You can let the script itself tell everything, instead of embedding documentation that may confuse readers of your script.

White Space.

Indentation

  • Use soft-tabs with a two space-indent.

  • Indent when as deep as case.

case
when song.name == 'Misty'
  puts 'Not again!'
when song.duration > 120
  puts 'Too long!'
when Time.now.hour > 21
  puts "It's too late"
else
  song.play
end

kind = case year
       when 1850..1889 then 'Blues'
       when 1890..1909 then 'Ragtime'
       when 1910..1929 then 'New Orleans Jazz'
       when 1930..1939 then 'Swing'
       when 1940..1950 then 'Bebop'
       else 'Jazz'
       end
  • Align function parameters either all on the same line or one per line.[link]
# bad
def self.create_translation(phrase_id, phrase_key, target_locale,
                            value, user_id, do_xss_check, allow_verification)
  ...
end

# good
def self.create_translation(phrase_id,
                            phrase_key,
                            target_locale,
                            value,
                            user_id,
                            do_xss_check,
                            allow_verification)
  ...
end

# good
def self.create_translation(
  phrase_id,
  phrase_key,
  target_locale,
  value,
  user_id,
  do_xss_check,
  allow_verification
)
  ...
end
  • Indent succeeding lines in multi-line boolean expressions.
# bad
def is_eligible?(user)
  Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) &&
  is_in_program?(user) &&
  program_not_expired
end

# good
def is_eligible?(user)
  Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) &&
    is_in_program?(user) &&
    program_not_expired
end

Inline

  • Never leave trailing whitespace.

  • When making inline comments, include a space between the end of the code and the start of your comment.


# bad
result = func(a, b)# we might want to change b to c

# good
result = func(a, b) # we might want to change b to c

  • Use spaces around operators; after commas, colons, and semicolons; and around { and before }.
sum = 1 + 2
a, b = 1, 2
1 > 2 ? true : false; puts 'Hi'
[1, 2, 3].each { |e| puts e }
  • Never include a space before a comma.
result = func(a, b)
  • Do not include space inside block parameter pipes. Include one space between parameters in a block. Include one space outside block parameter pipes.
# bad
{}.each { | x,  y |puts x }

# good
{}.each { |x, y| puts x }
  • Do not leave space between ! and its argument.

!something

  • No spaces after (, [ or before ], ).
some(arg).other
[1, 2, 3].length
  • Omit whitespace when doing string interpolation.
# bad
var = "This #{ foobar } is interpolated."

# good
var = "This #{foobar} is interpolated."

  • Don't use extra whitespace in range literals.
# bad
(0 ... coll).each do |item|

# good
(0...coll).each do |item|

Newlines

  • Add a new line after if conditions spanning multiple lines to help differentiate between the conditions and the body.
if @reservation_alteration.checkin == @reservation.start_date &&
   @reservation_alteration.checkout == (@reservation.start_date + @reservation.nights)

  redirect_to_alteration @reservation_alteration
end
  • Add a new line after conditionals, blocks, case statements, etc.[link]
if robot.is_awesome?
  send_robot_present
end

robot.add_trait(:human_like_intelligence)
  • Don’t include newlines between areas of different indentation (such as around class or module bodies).
# bad
class Foo

  def bar
    # body omitted
  end

end

# good
class Foo
  def bar
    # body omitted
  end
end
  • Include one, but no more than one, new line between methods.
def a
end

def b
end
  • Use a single empty line to break between statements to break up methods into logical paragraphs internally.
def transformorize_car
  car = manufacture(options)
  t = transformer(robot, disguise)

  car.after_market_mod!
  t.transform(car)
  car.assign_cool_name!

  fleet.add(car)
  car
end
  • End each file with a newline. Don't include multiple newlines at the end of a file.

Blank Lines

  • Between sections of a source file .

  • Between class and module definitions .

  • Between methods.

  • Before blocks or single-line comments.

  • Between logical sections inside a method to improve readability .

  • Use empty lines to break up a long method into logical paragraphs.

  • Before the return value of a method (unless it only has one line).

Blank spaces

  • Use spaces around operators, after commas, colons and semicolons, around { and before }.

  • No spaces after (, [ and before ], ).

  • No spaces between method names and ( in method parameters or invocation of method.

  • Use two spaces before statement modifiers (postfix if/unless/while/until/rescue).

  • A keyword followed by a parenthesis should be separated by a space.

while (foo.end?) {
<statements>
}
  • The number of spaces should be balanced.
a+b ## Correct

a + b ## Correct

a+ b ## AVOID!

a +b ## AVOID! (Erroneous: interpreted as a(+b))

Naming

The only real difficulties in programming are cache invalidation and naming things.
-- Phil Karlton

  • Name identifiers in English.
# bad - identifier using non-ascii characters
заплата = 1_000

# bad - identifier is a Bulgarian word, written with Latin letters (instead of Cyrillic)
zaplata = 1_000

# good
salary = 1_000
  • Use snake_case for methods and variables.
# bad
:'some symbol'
:SomeSymbol
:someSymbol

someVar = 5
var_10  = 10

def someMethod
  # some code
end

def SomeMethod
  # some code
end

# good
:some_symbol

some_var = 5
var10    = 10

def some_method
  # some code
end
  • Use CamelCase for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.).
  • Use SCREAMING_SNAKE_CASE for other constants.
# bad
SomeConst = 5

# good
SOME_CONST = 5
  • The names of predicate methods (methods that return a boolean value) should end in a question mark. (i.e. Array#empty?).
  • The names of potentially "dangerous" methods (i.e. methods that modify self or the arguments, exit!, etc.) should end with an exclamation mark. Bang methods should only exist if a non-bang method exists. (More on this.)
  • Name throwaway variables _.
version = '3.2.1'
major_version, minor_version, _ = version.split('.')
  • Do not separate numbers from letters on symbols, methods and variables.
# bad
:some_sym_1

some_var_1 = 1

def some_method_1
  # some code
end

# good
:some_sym1

some_var1 = 1

def some_method1
  # some code
end
  • Use CamelCase for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.)
# bad
class Someclass
  # some code
end

class Some_Class
  # some code
end

class SomeXml
  # some code
end

class XmlSomething
  # some code
end

# good
class SomeClass
  # some code
end

class SomeXML
  # some code
end

class XMLSomething
  # some code
end
  • Avoid prefixing predicate methods with the auxiliary verbs such as is, does, or can. These words are redundant and inconsistent with the style of boolean methods in the Ruby core library, such as empty? and include?.
# bad
class Person
  def is_tall?
    true
  end

  def can_play_basketball?
    false
  end

  def does_like_candy?
    true
  end
end

# good
class Person
  def tall?
    true
  end

  def basketball_player?
    false
  end

  def likes_candy?
    true
  end
end
  • The names of potentially dangerous methods (i.e. methods that modify self or the arguments, exit! (doesn't run the finalizers like exit does), etc.) should end with an exclamation mark if there exists a safe version of that dangerous method.
# bad - there is no matching 'safe' method
class Person
  def update!
  end
end

# good
class Person
  def update
  end
end

# good
class Person
  def update!
  end

  def update
  end
end
  • Define the non-bang (safe) method in terms of the bang (dangerous) one if possible.
class Array
  def flatten_once!
    res = []

    each do |e|
      [*e].each { |f| res << f }
    end

    replace(res)
  end

  def flatten_once
    dup.flatten_once!
  end
end
  • When defining binary operators, name the parameter other(<< and [] are exceptions to the rule, since their semantics are different).
def +(other)
  # body omitted
end

Use on Rails.

Directory and file names

• Directory and file names are lower case.

Ex: Foo class => foo.rb

Bar module => bar.rb

FooBar class => foo_bar.rb

Foo::Bar class => foo.rb

Classes/Modules

• Class and module names should be nouns; in mixed-case with the first letter of each internal word capitalized. (Keep acronyms like HTTP, RFC, XML uppercase.)

Ex: class Raster, class Raster::ImageSprite

Methods

  • Methods should be verbs. All lower case ASCII letters with words separated by underscore ('_').

Ex: run(), run_fast(), obj.background_color()

Variables

  • Variable names should be all lower case ASCII letters with words separated by underscore ('_').

Ex: i = 1, some_char = SomeChar.new(), table_width = 0.0

  • The length of an identifier determines its scope. Use one-letter variables for short block/method parameters, according to this scheme:

    • a,b,c: any object
    • d: directory names
    • e: elements of an Enumerable
    • ex: rescued exceptions
    • f: files and file names
    • i,j: indexes
    • k: the key part of a hash entry
    • m: methods
    • o: any object
    • r: return values of short methods
    • s: strings
    • v: any value
    • v: the value part of a hash entry
    • x,y,z: numbers
  • And in general, the first letter of the class name if all objects are of that type

  • Use _ or names prefixed with _ for unused variables.

  • When using inject with short blocks, name the arguments |a, e| (mnemonic: accumulator, element) .

  • When defining binary operators, name the argument "other".

Instance Variables

  • Instance variables are defined using the single "at" sign (@) followed by a name. It is suggested that a lowercase letter should be used after the @.

Ex: @colour, @lines

Global Variables

  • Starts with a dollar ($) sign followed by other characters.

Ex: $global

Constants

  • Constants should be all upper case with words separated by underscores ('_').

Ex: MIN_LENGTH = 1, DEFAULT_HOST = "foo.example.com"

Reduce repetition

  • When successive lines of a script share something,
x = ModuleA::ClassB::method_c(a)

y = ModuleA::ClassB::method_d(b)
  • you should make it like this:
cb = ModuleA::ClassB

x = cb::method_c(a)

y = cb::method_d(b)

  • You can also do:
include ModuleA

x = ClassB::method_c(a)

y = ClassB::method_d(b)

Database Table

  • Table names have all lowercase letters and underscores between words, also all table names must be plural noun, e.g. invoice_items, orders.

Model

  • The model is named using the class naming convention of unbroken MixedCase and is always the singular of the table name, e.g. table name might be orders, the model name would be Order.

  • The name of model is the name of database table in singular form.

  • Rails will then look for the class definition in a file called order.rb in the /app/models directory. If the model class name has multiple capitalized words, the table name is assumed to have underscores between these words.

Controller

  • Controller class names are pluralized, such that OrdersController would be the controller class for the orders table. Rails will then look for the class definition in a file called orders_controller.rb in the /app/controllers directory.

  • One controller class contains no more than 10 actions (for public method).

  • There's maximum 7 rows per action method.

Primary Key

  • The primary key of a table is assumed to be named id.

Foreign Key

  • The foreign key is named with the singular version of the target table name with id appended to it, e.g, order_id in the items table where we have items linked to the orders table.

Many to many Link Tables

  • Tables used to join two tables in a many to many relationship is named using the table named they link, with the table names in alphabetical order, for example items_orders.

Automated Record Timestamps

  • You can get ActiveRecord to automatically update the create and update times of records in a database table. To do this create two specially named columns created_at and updated_at to your table, e.g. t.datetime :created_at, and t.datetime :updated_at. If you only want to store the date rather than a date and time, use :created_on and :updated_on.

Class Organization

  1. Constants

  2. Layouts

  3. Include statements

  4. Mixins

  5. Filters

  6. attr_accessor, et al

  7. Associations

  8. Validations

  9. accepts_nested_attributes_for declarations

  10. Delegations

  11. Special declarative blocks, such as searchable and state_machine

  12. Methods (listed below)

Controller organization

  1. Before filter(before_filter) should be added at the top of controller.

  2. Before filter implementation should be immediate after filter declaration.

  3. Standard rails actions.

  4. Own custom actions.

  5. Inter-related actions should be clubbed together.

  6. Try to use of protected, private methods and they should be declared at the bottom of page in order.

Model organization

  1. Header section

    • Schema information at header section
    • Require files
  2. Top of class

    • Library or include methods
    • Model relationships
    • Virtual attributes
    • Active Record validations
  3. Middle of class

    • Custom validations
    • Public methods
  4. Bottom of class

    • Protected methods
    • Private methods

Code

  • Skinny Controllers, Fat models - If a controller method is more than a few lines long then think very carefully about what you’re doing.

  • Views should have very very little ruby in them and certainly shouldn’t touch the Databases.

  • If something requires more than one commit then do it in a branch. Almost everything should take more than one commit.

  • Use plugins only if they’re exactly what you need. Do not cargo cult.

  • In Ruby Regexes \A is the beginning of the string and \z is the end, ^ and $ also match the beginning and end of lines. You almost always want \A and \z, especially in input validations.

  • Try to keep initializers limited to config.

  • Make sure your calls to the database are including everything they need to in the original call, N+1 problems are way too common in most rails apps.

  • RESTful controllers, they’re much easier to navigate and generally more secure.

  • Ternaries (?:) are good if they fit on one line (remember the short lines rule).

  • ||= is good

  • def self.method to define singleton methods not class << self

  • Select the appropriate columns in a database call if you don’t need everything and the table has lots of data.

  • Migrations go up AND down - they maintain database structure not data.

  • Test first all the time unless you’re prototyping. If you’re prototyping then either you throw the code away afterwards or you have to convince someone else to write tests for all of it.

  • Blocks should be {|x| ... } on one line and do |x|...end on multiple lines.

  • One line if statements when appropriate.

  • A ridiculously large number of Railsy plugins use single table inheritance for things that it will turn out that you want to search over, avoid them if you want to be able to scale at all.

  • Use asset_packager - JavaScript and CSS Asset Compression for Production Rails Apps

  • Use indexes in database any time the column appears in the WHERE, GROUP BY or ORDER BY clause.

  • Don’t commit commented out code - It makes everything confusing and it’s in the version control anyway.

Method

Method definitions

  • Use def with parentheses when there are parameters. Omit the parentheses when the method doesn't accept any parameters.[link]
def some_method
  # body omitted
end

def some_method_with_parameters(arg1, arg2)
  # body omitted
end
  • Do not use default positional arguments. Use keyword arguments (if available - in Ruby 2.0 or later) or an options hash instead.
# bad
def obliterate(things, gently = true, except = [], at = Time.now)
  ...
end

# good
def obliterate(things, gently: true, except: [], at: Time.now)
  ...
end

# good
def obliterate(things, options = {})
  options = {
    :gently => true, # obliterate with soft-delete
    :except => [], # skip obliterating these things
    :at => Time.now, # don't obliterate them until later
  }.merge(options)

  ...
end
  • Avoid single-line methods. Although they are somewhat popular in the wild, there are a few peculiarities about their definition syntax that make their use undesirable.
# bad
def too_much; something; something_else; end

# good
def some_method
  # body
end

Method calls

Use parentheses for a method call:

  • If the method returns a value.
# bad
@current_user = User.find_by_id 1964192

# good
@current_user = User.find_by_id(1964192)
  • If the first argument to the method uses parentheses
# bad
put! (x + y) % len, value

# good
put!((x + y) % len, value)
  • Never put a space between a method name and the operating parenthesis
# bad
f (3 + 2) + 1

# good
f(3 + 2) + 1
  • Omit parentheses for a method call if the method accepts no arguments.
# bad
nil?()

# good
nil?
  • If the method doesn't return a value (or we don't care about the return), parentheses are optional. (Especially if the arguments overflow to multiple lines, parentheses may add readability).
# okay
render(:partial => 'foo')

# okay
render :partial => 'foo'
  • In either case:

    • If a method accept an options hash as the last argument, do not use { } during invocation.
# bad
get '/v1/reservations', { :id => 54875 }

# good
get '/v1/reservations', :id => 54875

Reference https://github.com/bbatsov/rails-style-guide

continues chapter 2...
pdkproitf 20-06-2017

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

pdkproitf

3 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
2 0
Welcome back, Before reading this chapter, let make sure that you already read through on chapter (Link). In previous chapter I already talk about ...
pdkproitf viết 8 tháng trước
2 0
White
1 0
This article will discuss about the difference between Rails 4 and Rails 5, what new in Rails 5. Before come to the details let take a quick look ...
pdkproitf viết 8 tháng trước
1 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
3 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!