Skip to content

Instantly share code, notes, and snippets.

@yoshida-eth0
Last active February 25, 2021 05:20
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 yoshida-eth0/2d47dd43438282fad686dd2b84a5b1d1 to your computer and use it in GitHub Desktop.
Save yoshida-eth0/2d47dd43438282fad686dd2b84a5b1d1 to your computer and use it in GitHub Desktop.
Dynamoidでembeds_many/embeds_one出来るようにする
require 'dynamoid'
module Dynamoid
def EmbedsMany(item_klass)
klass = DelegateClass(Array)
klass.include EmbedsManyBase
klass.const_set(:ITEM_CLASS, item_klass)
klass
end
module EmbedsManyBase
extend ActiveSupport::Concern
def dynamoid_dump
map(&:dynamoid_dump)
end
module ClassMethods
def dynamoid_load(serialized)
new((serialized || []).map{|item| self::ITEM_CLASS.dynamoid_load(item)})
end
end
end
module EmbeddedDocument
extend ActiveSupport::Concern
include Document
def dynamoid_dump
embeds_many_attributes = self.attributes
.select {|attr, value|
self.class.attributes[attr]
}.select {|attr, value|
Class===self.class.attributes[attr][:type]
}.select {|attr, value|
self.class.attributes[attr][:type].include?(EmbedsManyBase)
}
normal_attributes = self.attributes
.reject {|attr, value|
embeds_many_attributes.key?(attr)
}
dumped = Dynamoid::Dumping.dump_attributes(normal_attributes, self.class.attributes)
embeds_many_attributes.compact.each {|attr, value|
dumped[attr] = value.map {|value2|
Dynamoid::Dumping.dump_attributes(value2.attributes, value2.class.attributes)
}
}
dumped
end
module ClassMethods
def dynamoid_load(serialized)
undumped = Dynamoid::Undumping.undump_attributes(serialized, self.attributes)
self.new(undumped)
end
end
end
module TypeCasting
module EmbedsManyTypeCaster
def process(value)
field_class = @options[:type]
if field_class.include?(EmbedsManyBase)
item_class = field_class::ITEM_CLASS
item_attributes = item_class.attributes
items = value.map {|item|
if item.respond_to?(:attributes)
item = item.attributes
end
casted = TypeCasting.cast_attributes(item, item_attributes)
item_class.new(casted)
}
field_class.new(items)
else
super(value)
end
end
end
module EmbeddedDocumentTypeCaster
def process(value)
field_class = @options[:type]
if field_class.include?(EmbeddedDocument)
if value.respond_to?(:attributes)
value = value.attributes
end
casted = TypeCasting.cast_attributes(value, field_class.attributes)
field_class.new(casted)
else
super(value)
end
end
end
class CustomTypeCaster < Base
prepend EmbedsManyTypeCaster
prepend EmbeddedDocumentTypeCaster
end
end
module Dumping
module EmbedsManyDumper
def process(value)
field_class = @options[:type]
if field_class.include?(EmbedsManyBase)
item_class = field_class::ITEM_CLASS
item_attributes = item_class.attributes
value.map {|item|
if item.respond_to?(:attributes)
item = item.attributes
end
Dumping.dump_attributes(item, item_attributes)
}
else
super(value)
end
end
end
class CustomTypeDumper < Base
prepend EmbedsManyDumper
end
end
module Validations
module ClassMethods
def validates_associated(*attr_names)
validates_with(AssociatedValidator, _merge_attributes(attr_names))
end
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if Array(value).reject { |r| valid_object?(r) }.any?
record.errors.add(attribute, :invalid, **options.merge(value: value))
end
end
private
def valid_object?(record)
(record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
end
end
end
end
end
def index(event:, context:)
users = User.all.to_a
{
statusCode: 200,
body: {
users: users,
articles_class: users[0].articles.class.name,
article_class: users[0].articles.first.class.name,
comments_class: users[0].articles.first.comments.class.name,
comment_class: users[0].articles.first.comments.first.class.name,
profile_class: users[0].profile.class.name,
}.to_json
}
end
def create(event:, context:)
user = User.new(
name: "nameA",
articles: [
{
title: "titleA",
comments: [
{
name: 'commentA',
message: 'aaa',
},
{
name: 'commentB',
message: 'bbb',
},
],
},
],
profile: {
firstname: 'tanaka',
lastname: 'tarou',
},
)
begin
user.save
{
statusCode: 200,
body: {
user: user,
articles_class: user.articles.class.name,
article_class: user.articles.first.class.name,
comments_class: user.articles.first.comments.class.name,
comment_class: user.articles.first.comments.first.class.name,
profile_class: user.profile.class.name,
}.to_json
}
rescue Dynamoid::Errors::RecordNotUnique => e
{
statusCode: 400,
body: {
message: e.message,
}.to_json
}
end
end
def update(event:, context:)
id = event["pathParameters"]["id"]
begin
user = User.find(id)
user.articles << {
title: "titleB",
comments: [
{
name: 'commentC',
message: 'ccc',
},
],
}
user.profile.firstname = 'satou'
user.save
{
statusCode: 200,
body: {
user: user,
articles_class: user.articles.class.name,
article_class: user.articles.first.class.name,
comments_class: user.articles.first.comments.class.name,
comment_class: user.articles.first.comments.first.class.name,
profile_class: user.profile.class.name,
}.to_json
}
rescue Dynamoid::Errors::RecordNotFound => e
{
statusCode: 404,
body: {
message: e.message,
}.to_json
}
end
end
__END__
$ curl -X POST http://localhost:3000/local/users | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 450 100 450 0 0 350 0 0:00:01 0:00:01 --:--:-- 350
{
"user": {
"name": "nameA",
"articles": [
{
"title": "titleA",
"comments": [
{
"name": "commentA",
"message": "aaa"
},
{
"name": "commentB",
"message": "bbb"
}
]
}
],
"profile": {
"firstname": "tanaka",
"lastname": "tarou"
},
"created_at": "2021-02-13T17:12:22.123+09:00",
"updated_at": "2021-02-13T17:12:22.124+09:00",
"id": "517e4636-4260-4426-9050-ebe398db9a19"
},
"articles_class": null,
"article_class": "Article",
"comments_class": null,
"comment_class": "Comment",
"profile_class": "Profile"
}
$ curl -X PUT http://localhost:3000/local/users/517e4636-4260-4426-9050-ebe398db9a19 | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 517 100 517 0 0 384 0 0:00:01 0:00:01 --:--:-- 384
{
"user": {
"created_at": "2021-02-13T08:12:22.123+00:00",
"updated_at": "2021-02-13T17:12:47.339+09:00",
"id": "517e4636-4260-4426-9050-ebe398db9a19",
"name": "nameA",
"articles": [
{
"title": "titleA",
"comments": [
{
"name": "commentA",
"message": "aaa"
},
{
"name": "commentB",
"message": "bbb"
}
]
},
{
"title": "titleB",
"comments": [
{
"name": "commentC",
"message": "ccc"
}
]
}
],
"profile": {
"firstname": "satou",
"lastname": "tarou"
}
},
"articles_class": null,
"article_class": "Article",
"comments_class": null,
"comment_class": "Comment",
"profile_class": "Profile"
}
$ curl -X GET http://localhost:3000/local/users | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 520 100 520 0 0 411 0 0:00:01 0:00:01 --:--:-- 411
{
"users": [
{
"created_at": "2021-02-13T08:12:22.123+00:00",
"updated_at": "2021-02-13T08:12:47.339+00:00",
"id": "517e4636-4260-4426-9050-ebe398db9a19",
"name": "nameA",
"articles": [
{
"title": "titleA",
"comments": [
{
"name": "commentA",
"message": "aaa"
},
{
"name": "commentB",
"message": "bbb"
}
]
},
{
"title": "titleB",
"comments": [
{
"name": "commentC",
"message": "ccc"
}
]
}
],
"profile": {
"firstname": "satou",
"lastname": "tarou"
}
}
],
"articles_class": null,
"article_class": "Article",
"comments_class": null,
"comment_class": "Comment",
"profile_class": "Profile"
}
class Article
include Dynamoid::EmbeddedDocument
field :title, :string
field :comments, Dynamoid::EmbedsMany(Comment)
end
class Comment
include Dynamoid::EmbeddedDocument
field :name, :string
field :message, :string
end
class Profile
include Dynamoid::EmbeddedDocument
field :firstname, :string
field :lastname, :string
end
class User
include Dynamoid::Document
field :name, :string
field :articles, Dynamoid::EmbedsMany(Article)
field :profile, Profile
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment