Skip to content

Instantly share code, notes, and snippets.

@0legovich
Last active August 5, 2019 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0legovich/fb7c2ad2bb62a292d03bbcc43b9b70b5 to your computer and use it in GitHub Desktop.
Save 0legovich/fb7c2ad2bb62a292d03bbcc43b9b70b5 to your computer and use it in GitHub Desktop.
SaveResource module
# SaveResource предназначен для созданиия записи в БД с nested_attributes (или без них).
# Особенности:
# * если у записи уже есть persisted nested records и при обновлении их атрибуты не были переданы, то такие записи будут удалены;
# * перед сохранением записи в БД, с ней можно выполнить любое действие (например установить дефолтные параметры) с помощью передачи блока в конструктор.
# Примеры вызова:
def create
update_position = ->(operation) { operation.position = operation.user.positions.first! }
service = SaveResource::Common.new(Operation, params[:operation], &update_position)
if service.save
render json: { operation: service.record.as_json }
else
render status: 422, text: "Record #{service.error_record.class} invalid"
end
end
def update
service = SaveResource::WithManyNested.new(Kind, params[:kind], :kind_accounts) do |kind|
kind.parent_accounts.each { |account| kind.kind_accounts.find_or_create_by!(account_id: account.id) }
end
if service.save
render json: { kind: service.record.as_json }
else
render status: 422, text: "Record #{service.error_record.class} invalid"
end
end
# frozen_string_literal: true
module SaveResource
class SaveResourceError < StandardError; end;
class Base # :nodoc:
attr_reader :model, :attrs, :record, :block, :error_record
def initialize(model, attrs, &block)
@model = model
@attrs = attrs
@record = model.find_by(id: attrs[:id])&.lock! || model.new
@block = block
end
def save
raise NotImplementedError
end
private
def find_or_create
ActiveRecord::Base.transaction(requires_new: true) do
record.assign_attributes(attrs)
record.save(validate: false)
yield if block_given?
block&.(record)
record.save!
end
rescue ActiveRecord::RecordInvalid => e
@error_record = e.record
false
end
end
end
# frozen_string_literal: true
module SaveResource
class Common < Base # :nodoc:
def save
find_or_create
end
end
end
# frozen_string_literal: true
module SaveResource
class WithManyNested < Base # :nodoc:
attr_reader :association_name, :nested_collection, :persisted_nested_attrs, :new_nested_attrs
def initialize(model, attrs, association_name, &block)
super(model, attrs, &block)
nested_attrs = attrs.delete("#{association_name}_attributes")
@association_name = association_name
@nested_collection = record.public_send(association_name)
@persisted_nested_attrs, @new_nested_attrs = nested_attrs.each_with_object([[], []]) do |attr, acc|
(attr[:id] ? acc[0] : acc[1] ) << attr
end
end
def save
find_or_create do
update_association_records if persisted_nested_attrs.present?
create_association_records if new_nested_attrs.present?
end
end
def update_association_records
persisted_ids = persisted_nested_attrs.map { |attr| attr[:id] }
nested_collection.where(id: persisted_ids).each do |nested_record|
attrs = persisted_nested_attrs.find { |attr| attr[:id] == nested_record.id }
nested_record.update!(attrs)
end
delete_unused_association_records(persisted_ids)
end
def delete_unused_association_records(ids)
nested_collection.where.not(id: ids).destroy_all
end
def create_association_records
new_nested_attrs.each { |attrs| nested_collection.create!(attrs) }
end
end
end
# frozen_string_literal: true
module SaveResource
class WithOneNested < Base # :nodoc:
attr_reader :nested_attrs, :association_name, :nested_record
def initialize(model, attrs, association_name, &block)
super(model, attrs, &block)
@nested_attrs = attrs.delete("#{association_name}_attributes").with_indifferent_access
@association_name = association_name
@nested_record = record.public_send(association_name)
end
def save
find_or_create do
nested_record.present? ? update_nested_record : create_nested_record
end
end
private
def update_nested_record
if nested_attrs[:id] && nested_attrs[:id] != nested_record.id
raise SaveResourceError, 'Updated record is not match nested attributes'
end
nested_record.update!(nested_attrs)
end
def create_nested_record
record.public_send("create_#{association_name}!", nested_attrs)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment