Last active
August 5, 2019 14:03
-
-
Save 0legovich/fb7c2ad2bb62a292d03bbcc43b9b70b5 to your computer and use it in GitHub Desktop.
SaveResource module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module SaveResource | |
class Common < Base # :nodoc: | |
def save | |
find_or_create | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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