Spec-Zone.ru › Ruby on Rails 6.0

класс ActiveRecord::Relation

Родитель:
Объект
Включенные модули:
Enumerable, ActiveRecord::FinderMethods, ActiveRecord::Calculations, ActiveRecord::SpawnMethods, ActiveRecord::QueryMethods, ActiveRecord::Batches, ActiveRecord::Explain

Активное соотношение записей

Константы

CLAUSE_METHODS
НЕДОПУСТИМЫЕ_МЕТОДЫ_ДЛЯ_DELETE_ALL
MULTI_VALUE_METHODS
SINGLE_VALUE_METHODS
VALUE_METHODS

Атрибуты

klass[R]
loaded[R]
loaded?[R]
model[R]
predicate_builder[R]
skip_preloading_value[RW]
table[R]

Публичные методы класса

new(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 27
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
  @klass  = klass
  @table  = table
  @values = values
  @offsets = {}
  @loaded = false
  @predicate_builder = predicate_builder
  @delegate_to_klass = false
end

Общедоступные методы экземпляров

==(other) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 681
def ==(other)
  case other
  when Associations::CollectionProxy, AssociationRelation
    self == other.records
  when Relation
    other.to_sql == to_sql
  when Array
    records == other
  end
end

Сравнивает два отношения на равенство.

any?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 277
def any?
  return super if block_given?
  !empty?
end

Возвращает true, если есть записи.

Вызывает метод суперкласса
blank?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 697
def blank?
  records.blank?
end

Возвращает true, если отношение пустое.

build(attributes = nil, &block)
Псевдоним для: new
cache_key(timestamp_column = :updated_at) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 311
def cache_key(timestamp_column = :updated_at)
  @cache_keys ||= {}
  @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
end

Возвращает стабильный ключ кэша, который можно использовать для идентификации этого запроса. Ключ кэша построен с отпечатком SQL-запроса.

Product.where("name like ?", "%Cosmic Encounter%").cache_key
# => "products/query-1850ab3d302391b85b8693e941286659"

Если ActiveRecord::Base.collection_cache_versioning выключен, как это было в Rails 6.0 и ранее, ключ кэша также будет содержать версию.

ActiveRecord::Base.collection_cache_versioning = false
Product.where("name like ?", "%Cosmic Encounter%").cache_key
# => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"

Также можно передать пользовательский столбец со временем, чтобы получить отметку времени последнего обновлённого записей.

Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
cache_version(timestamp_column = :updated_at) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 338
def cache_version(timestamp_column = :updated_at)
  if collection_cache_versioning
    @cache_versions ||= {}
    @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
  end
end

Возвращает версию кэша, которая может быть использована вместе с ключом кэша для формирования схемы повторно используемого кэширования. Версия кэша построена с количеством записей, соответствующих запросу, и отметкой времени последней обновленной записи. Когда новая запись соответствует запросу или любая из существующих записей обновляется или удаляется, версия кэша изменяется.

Если коллекция загружена, метод будет перебирать записи, чтобы сгенерировать отметку времени, в противном случае он вызовет один SQL-запрос, такой как:

SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
create(attributes = nil, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 95
def create(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    block = _deprecated_scope_block("create", &block)
    scoping { klass.create(attributes, &block) }
  end
end

Попытка создать новую запись с теми же ограниченными атрибутами, определенными в отношении. Возвращает инициализированный объект, если проверка валидации не пройдена.

Ожидает аргументы в том же формате, что и ActiveRecord::Base.create.

Примеры

users = User.where(name: 'Oscar')
users.create # => #<User id: 3, name: "Oscar", ...>

users.create(name: 'fxn')
users.create # => #<User id: 4, name: "fxn", ...>

users.create { |user| user.name = 'tenderlove' }
# => #<User id: 5, name: "tenderlove", ...>

users.create(name: nil) # validation on name
# => #<User id: nil, name: nil, ...>
create!(attributes = nil, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 110
def create!(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create!(attr, &block) }
  else
    block = _deprecated_scope_block("create!", &block)
    scoping { klass.create!(attributes, &block) }
  end
end

Аналогично create, но вызывает create! в базовом классе. Выбрасывает исключение, если возникает ошибка валидации.

Ожидает аргументы в том же формате, что и ActiveRecord::Base.create!.

create_or_find_by(attributes, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 209
def create_or_find_by(attributes, &block)
  transaction(requires_new: true) { create(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
  find_by!(attributes)
end

Попытка создать запись с заданными атрибутами в таблице, которая имеет ограничение уникальности на одном или нескольких столбцах. Если строка уже существует с одним или несколькими из этих уникальных ограничений, исключение, которое обычно возникает при такой вставке, перехватывается, и существующая запись с этими атрибутами находится с помощью find_by!.

Это похоже на find_or_create_by, но избегает проблемы с устаревшими чтениями между SELECT и INSERT, поскольку этому методу сначала необходимо запросить таблицу, а затем попытаться вставить строку, если она не найдена.

Однако существует несколько недостатков метода create_or_find_by:

  • Базовая таблица должна иметь соответствующие столбцы, определённые с ограничениями уникальности.

  • Нарушение уникального ограничения может быть вызвано только одним или хотя бы меньшим количеством заданных атрибутов. Это означает, что последующий find_by! может не найти соответствующую запись, что затем вызовет исключение ActiveRecord::RecordNotFound , а не запись с заданными атрибутами.

  • Хотя мы избегаем состояния гонки между SELECT -> INSERT из find_or_create_by, у нас есть ещё одна гонка между INSERT -> SELECT, которая может быть вызвана, если другой клиент выполняет DELETE между этими двумя операциями. Но для большинства приложений это значительно менее вероятное условие.

  • Он полагается на обработку исключений для управления потоком, что может быть немного медленнее.

  • Первичный ключ может автоматически увеличиваться при каждом создании, даже если он не удастся. Это может ускорить проблему исчерпания целых чисел, если базовая таблица всё ещё использует первичный ключ типа int (примечание: все приложения Rails с версии 5.1+ по умолчанию используют bigint, что не подвержено этой проблеме).

Этот метод вернёт запись, если все заданные атрибуты охвачены уникальными ограничениями (если не будет вызвана гонка INSERT -> DELETE -> SELECT), но если попытка создания завершилась неудачей из-за ошибок валидации, она не будет сохранена, вы получите то, что возвращает create в такой ситуации.

create_or_find_by!(attributes, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 218
def create_or_find_by!(attributes, &block)
  transaction(requires_new: true) { create!(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
  find_by!(attributes)
end

Как и create_or_find_by, но вызывает create!, поэтому исключение генерируется, если созданная запись некорректна.

delete_all() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 554
def delete_all
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
    value = @values[method]
    method == :distinct ? value : value&.any?
  end
  if invalid_methods.any?
    raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
  end

  if eager_loading?
    relation = apply_join_dependency
    return relation.delete_all
  end

  stmt = Arel::DeleteManager.new
  stmt.from(arel.join_sources.empty? ? table : arel.source)
  stmt.key = arel_attribute(primary_key)
  stmt.take(arel.limit)
  stmt.offset(arel.offset)
  stmt.order(*arel.orders)
  stmt.wheres = arel.constraints

  affected = @klass.connection.delete(stmt, "#{@klass} Destroy")

  reset
  affected
end

Удаляет записи без предварительной инициализации записей, и, следовательно, без вызова метода #destroy и без вызова обратных вызовов. Это единственное SQL-выражение DELETE, которое направляется непосредственно в базу данных, намного эффективнее, чем destroy_all. Однако будьте осторожны с отношениями, в частности, правила :dependent , определенные на ассоциациях, не учитываются. Возвращает количество затронутых строк.

Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all

Оба вызова удаляют затронутые записи сразу одним оператором DELETE. Если вам необходимо уничтожить зависимые ассоциации или вызвать ваши before_* или after_destroy обратные вызовы, используйте метод destroy_all вместо этого.

Если передается недопустимый метод, delete_all вызовет исключение ActiveRecordError:

Post.distinct.delete_all
# => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
delete_by(*args) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 604
def delete_by(*args)
  where(*args).delete_all
end

Ищет и удаляет все записи, соответствующие указанным условиям. Это сокращенная форма relation.where(condition).delete_all. Возвращает количество затронутых строк.

Если запись не найдена, возвращает 0 , так как строк не затронуто.

Person.delete_by(id: 13)
Person.delete_by(name: 'Spartacus', rating: 4)
Person.delete_by("published_at < ?", 2.weeks.ago)
destroy_all() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 532
def destroy_all
  records.each(&:destroy).tap { reset }
end

Уничтожает записи, инициализируя каждую запись и вызывая её метод #destroy. Выполняются обратные вызовы каждого объекта (включая :dependent параметры ассоциации). Возвращает коллекцию объектов, которые были уничтожены; каждый будет заморожен, чтобы отразить, что никаких изменений не должно быть внесено (так как они не могут быть сохранены).

Примечание: Инициализация, выполнение обратных вызовов и удаление каждой записи могут занимать много времени, когда вы удаляете сразу много записей. Он генерирует по крайней мере один SQL DELETE запрос на запись (или возможно больше, для выполнения ваших обратных вызовов). Если вы хотите быстро удалить много строк, не беспокоясь об их ассоциациях или обратных вызовах, используйте delete_all вместо этого.

Примеры

Person.where(age: 0..18).destroy_all
destroy_by(*args) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 591
def destroy_by(*args)
  where(*args).destroy_all
end

Находит и удаляет все записи, соответствующие указанным условиям. Это сокращённая запись для relation.where(condition).destroy_all. Возвращает коллекцию объектов, которые были удалены.

Если записи не найдено, возвращает пустой массив.

Person.destroy_by(id: 13)
Person.destroy_by(name: 'Spartacus', rating: 4)
Person.destroy_by("published_at < ?", 2.weeks.ago)
eager_loading?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 666
def eager_loading?
  @should_eager_load ||=
    eager_load_values.any? ||
    includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end

Возвращает true, если для связи требуется жадное загрузку.

empty?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 265
def empty?
  return @records.empty? if loaded?
  !exists?
end

Возвращает true, если записей нет.

encode_with(coder) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 255
def encode_with(coder)
  coder.represent_seq(nil, records)
end

Сериализует объекты связи Массив.

explain() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 239
def explain
  exec_explain(collecting_queries_for_explain { exec_queries })
end

Выполняет EXPLAIN для запроса или запросов, инициированных этим отношением, и возвращает результат в виде строки. Строка форматируется, имитируя вывод командной оболочки базы данных.

Обратите внимание, что этот метод фактически выполняет запросы, поскольку результаты некоторых из них необходимы для последующих запросов при жадной загрузке.

Дополнительные сведения см. в руководстве по интерфейсу запросов Active Record.

find_or_create_by(attributes, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 168
def find_or_create_by(attributes, &block)
  find_by(attributes) || create(attributes, &block)
end

Находит первую запись с заданными атрибутами или создаёт запись с этими атрибутами, если она не найдена:

# Find the first user named "Penélope" or create a new one.
User.find_or_create_by(first_name: 'Penélope')
# => #<User id: 1, first_name: "Penélope", last_name: nil>

# Find the first user named "Penélope" or create a new one.
# We already have one so the existing record will be returned.
User.find_or_create_by(first_name: 'Penélope')
# => #<User id: 1, first_name: "Penélope", last_name: nil>

# Find the first user named "Scarlett" or create a new one with
# a particular last name.
User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
# => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">

Этот метод принимает блок, который передаётся в create. Последний пример выше можно переписать следующим образом:

# Find the first user named "Scarlett" or create a new one with a
# different last name.
User.find_or_create_by(first_name: 'Scarlett') do |user|
  user.last_name = 'Johansson'
end
# => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">

Этот метод всегда возвращает запись, но если попытка создания завершилась ошибкой валидации, она не будет сохранена; вы получите то, что возвращает create в такой ситуации.

Обратите внимание, что этот метод не атомарный, он сначала выполняет SELECT, а если результатов нет, пытается выполнить INSERT. Если есть другие потоки или процессы, возникает гонка между этими двумя вызовами, и может возникнуть ситуация, когда у вас появятся две похожие записи.

Если это может быть проблемой для вашего приложения, обратитесь к create_or_find_by.

find_or_create_by!(attributes, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 175
def find_or_create_by!(attributes, &block)
  find_by(attributes) || create!(attributes, &block)
end

Аналогично find_or_create_by, но вызывает create!, поэтому исключение генерируется, если созданная запись некорректна.

find_or_initialize_by(attributes, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 226
def find_or_initialize_by(attributes, &block)
  find_by(attributes) || new(attributes, &block)
end

Подобно find_or_create_by, но вызывает new вместо create.

initialize_copy(other) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 37
def initialize_copy(other)
  @values = @values.dup
  reset
end
inspect() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 705
def inspect
  subject = loaded? ? records : self
  entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)

  entries[10] = "..." if entries.size == 11

  "#<#{self.class.name} [#{entries.join(', ')}]>"
end
joined_includes_values() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 676
def joined_includes_values
  includes_values & joins_values
end

Соединения, которые также помечены для предварительной загрузки. В этом случае мы должны просто выполнить жадную загрузку. Обратите внимание, что это простое реализация, потому что у нас могут быть строки и символы, которые представляют одну и ту же ассоциацию, но не совпадают с этой реализацией. Также у нас могут быть вложенные массивы, которые частично совпадают, например, { a: :b } & { a: [:b, :c] }

load(&block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 614
def load(&block)
  exec_queries(&block) unless loaded?

  self
end

Заставляет загрузить записи из базы данных, если они ещё не загружены. Вы можете использовать это, если по какой-то причине вам нужно явно загрузить некоторые записи перед фактическим использованием.

Значение, возвращаемое этой функцией, - это само отношение, а не сами записи.

Post.where(published: true).load # => #<ActiveRecord::Relation>
many?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 289
def many?
  return super if block_given?
  limit_value ? records.many? : size > 1
end

Возвращает true, если записей больше одной.

Вызывает метод родительского класса Enumerable#many?
new(attributes = nil, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 69
def new(attributes = nil, &block)
  block = _deprecated_scope_block("new", &block)
  scoping { klass.new(attributes, &block) }
end

Инициализирует новую запись из отношения, сохраняя текущий контекст.

Ожидает аргументы в том же формате, что и ActiveRecord::Base.new.

users = User.where(name: 'DHH')
user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>

Вы также можете передать блок в new, используя новую запись в качестве аргумента:

user = users.new { |user| user.name = 'Oscar' }
user.name # => Oscar
Также алиасирован как: build
none?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 271
def none?
  return super if block_given?
  empty?
end

Возвращает true, если записей нет.

Вызывает метод родительского класса
one?() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 283
def one?
  return super if block_given?
  limit_value ? records.one? : size == 1
end

Возвращает true, если ровно одна запись.

Вызывает метод родительского класса
pretty_print(q) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 692
def pretty_print(q)
  q.pp(records)
end
reload() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 621
def reload
  reset
  load
end

Принудительно перезагружает отношение.

reset() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 626
def reset
  @delegate_to_klass = false
  @_deprecated_scope_source = nil
  @to_sql = @arel = @loaded = @should_eager_load = nil
  @records = [].freeze
  @offsets = {}
  self
end
scope_for_create() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 661
def scope_for_create
  where_values_hash.merge!(create_with_value.stringify_keys)
end
scoping() { |: _scoping(self) { yield }| ... } Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 397
def scoping
  already_in_scope? ? yield : _scoping(self) { yield }
end

Ограничивает все запросы текущим контекстом.

Comment.where(post_id: 1).scoping do
  Comment.first
end
# => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1

Обратитесь к unscoped, если вы хотите удалить все предыдущие контексты (включая default_scope) во время выполнения блока.

size() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 260
def size
  loaded? ? @records.length : count(:all)
end

Возвращает размер записей.

to_a()
Псевдоним для: to_ary
to_ary() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 244
def to_ary
  records.dup
end

Преобразует объекты отношения в Массив.

Также псевдоним для: to_a
to_sql() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 639
def to_sql
  @to_sql ||= begin
    if eager_loading?
      apply_join_dependency do |relation, join_dependency|
        relation = join_dependency.apply_column_aliases(relation)
        relation.to_sql
      end
    else
      conn = klass.connection
      conn.unprepared_statement { conn.to_sql(arel) }
    end
  end
end

Возвращает SQL-запрос для отношения.

User.where(name: 'Oscar').to_sql
# => SELECT "users".* FROM "users"  WHERE "users"."name" = 'Oscar'
touch_all(*names, time: nil) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 512
def touch_all(*names, time: nil)
  update_all klass.touch_attributes_with_time(*names, time: time)
end

Обновляет все записи в текущем отношении без предварительного создания записей с атрибутами updated_at/updated_on, установленных на текущее время или указанное время. Этот метод может принимать имена атрибутов и необязательный аргумент времени. Если передаются имена атрибутов, они обновляются вместе с атрибутами updated_at/updated_on. Если аргумент времени не передан, используется текущее время по умолчанию.

Примеры

# Touch all records
Person.all.touch_all
# => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"

# Touch multiple records with a custom attribute
Person.all.touch_all(:created_at)
# => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"

# Touch multiple records with a specified time
Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
# => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"

# Touch records with scope
Person.where(name: 'David').touch_all
# => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
update_all(updates) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 432
def update_all(updates)
  raise ArgumentError, "Empty list of attributes to change" if updates.blank?

  if eager_loading?
    relation = apply_join_dependency
    return relation.update_all(updates)
  end

  stmt = Arel::UpdateManager.new
  stmt.table(arel.join_sources.empty? ? table : arel.source)
  stmt.key = arel_attribute(primary_key)
  stmt.take(arel.limit)
  stmt.offset(arel.offset)
  stmt.order(*arel.orders)
  stmt.wheres = arel.constraints

  if updates.is_a?(Hash)
    if klass.locking_enabled? &&
        !updates.key?(klass.locking_column) &&
        !updates.key?(klass.locking_column.to_sym)
      attr = arel_attribute(klass.locking_column)
      updates[attr.name] = _increment_attribute(attr)
    end
    stmt.set _substitute_values(updates)
  else
    stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
  end

  @klass.connection.update stmt, "#{@klass} Update All"
end

Обновляет все записи в текущем отношении с указанными данными. Этот метод строит один SQL-запрос UPDATE и отправляет его непосредственно в базу данных. Он не создаёт связанные модели и не вызывает обратные вызовы или валидации Active Record. Тем не менее, значения, переданные в update_all, все равно будут проходить через стандартную типизацию и сериализацию Active Record.

Примечание: Поскольку обратные вызовы Active Record не вызываются, этот метод не будет автоматически обновлять столбцы updated_at/updated_on.

Параметры

  • updates - Строка, массив или хеш, представляющие часть SET SQL-запроса.

Примеры

# Update all customers with the given attributes
Customer.update_all wants_email: true

# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')

# Update all books that match conditions, but limit it to 5 ordered by date
Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')

# Update all invoices and set the number column to its id value.
Invoice.update_all('number = id')
values() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 701
def values
  @values.dup
end
where_values_hash(relation_table_name = klass.table_name) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 657
def where_values_hash(relation_table_name = klass.table_name)
  where_clause.to_h(relation_table_name)
end

Возвращает хеш условий WHERE.

User.where(name: 'Oscar').where_values_hash
# => {name: "Oscar"}

Защищённые методы экземпляра

load_records(records) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 742
def load_records(records)
  @records = records.freeze
  @loaded = true
end

© 2004–2019 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API