entitainer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/lib/entitainer.rb +185 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e072a6a4a7c887339670ad5bd9f38cce883c89fc1930105c5f303049f6af0621
|
4
|
+
data.tar.gz: 34e36a7caf77b65ac83508385fba12fdb09767cd7b020f379180f695048a8554
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dfa795cb39d12a392cc65c1172fa7cc1d7fa25b37e9a539315ba5135e32dc0e4599202f664ad4be92b2ef4fee9a330b70b27eb9b79151a76e7b3e501db8b4ba4
|
7
|
+
data.tar.gz: f66c7e7ee186bb0f575447fce22fb35050f96319545de28eec70080399ed07d30c181d431fcaac3bff428e2f27b4343e91c5c1a8c0b878e7bde3a66cc806e802
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Mr Dev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# entitainer
|
2
|
+
Immutable representations of database entities.
|
3
|
+
|
4
|
+
Entitainer helps turning database relations into objects.
|
5
|
+
Simplifies defining entity attributes and relations between different entities.
|
6
|
+
|
7
|
+
# Examples
|
8
|
+
|
9
|
+
```
|
10
|
+
class Artist
|
11
|
+
include Entitainer
|
12
|
+
|
13
|
+
schema do
|
14
|
+
attributes :name
|
15
|
+
|
16
|
+
has_many :albums
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Album
|
21
|
+
include Entitainer
|
22
|
+
|
23
|
+
schema do
|
24
|
+
attributes :title,
|
25
|
+
:date
|
26
|
+
|
27
|
+
belongs_to :artist
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
## Attributes
|
33
|
+
```
|
34
|
+
Artist.available_attributes
|
35
|
+
# => [:id, :name]
|
36
|
+
Album.available_attributes #
|
37
|
+
# => [:id, :title, :date, :artist_id]
|
38
|
+
|
39
|
+
artist = Artist.new
|
40
|
+
artist.defined_attributes
|
41
|
+
# => []
|
42
|
+
artist = Artist.new(name: 'Czarny motyl')
|
43
|
+
artist.defined_attributes
|
44
|
+
# => [:name]
|
45
|
+
artist.defined_attributes_with_values
|
46
|
+
# => {:name=>"Czarny motyl"}
|
47
|
+
```
|
48
|
+
|
49
|
+
## Belongs to relation
|
50
|
+
```
|
51
|
+
artist = Artist.new(id: 1, name: 'Czarny motyl')
|
52
|
+
album = Album.new(title: 'Maszyna do robienia dymu', artist: artist)
|
53
|
+
|
54
|
+
album.artist.name
|
55
|
+
# => "Czarny motyl"
|
56
|
+
album.artist.id
|
57
|
+
# => 1
|
58
|
+
album.artist_id
|
59
|
+
# => 1
|
60
|
+
```
|
61
|
+
|
62
|
+
## Has many relation
|
63
|
+
```
|
64
|
+
artist = Artist.new(name: 'Czarny motyl') do |a|
|
65
|
+
a.albums << Album.new(title: 'Maszyna do robienia dymu')
|
66
|
+
a.albums << Album.new(title: 'Maszyna do suszenia łez')
|
67
|
+
end
|
68
|
+
|
69
|
+
artist.albums.map(&:title)
|
70
|
+
# => ["Maszyna do robienia dymu", "Maszyna do suszenia łez"]
|
71
|
+
|
72
|
+
```
|
73
|
+
|
data/lib/entitainer.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optiomist'
|
4
|
+
|
5
|
+
# Build database entities.
|
6
|
+
# Define attributes and relation in a _schema_ block
|
7
|
+
# schema do
|
8
|
+
# attributes :attr1, :attr2, :attr3
|
9
|
+
# belongs_to :parent1, :parent2
|
10
|
+
# has_many :items1, :items2
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# An _id_ attribute is always added automatically to the attribute list.
|
14
|
+
#
|
15
|
+
# Also for <tt>belongs_to</tt> relation an attribute with the same name as
|
16
|
+
# the relation and <tt>_id</tt> suffix is added to the attribute list.
|
17
|
+
module Entitainer
|
18
|
+
def self.included(base)
|
19
|
+
base.extend(ClassMethods)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Methods for schema definition.
|
23
|
+
module ClassMethods
|
24
|
+
# A collection of all available attributes defined in a schema block,
|
25
|
+
# <tt>id</tt> attribute, and <tt>_id</tt> sufixed attribute for each
|
26
|
+
# <tt>belongs_to</tt> relation.
|
27
|
+
def available_attributes
|
28
|
+
@available_attributes ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
def available_belongs_tos
|
32
|
+
@available_belongs_tos ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
def available_has_manys
|
36
|
+
@available_has_manys ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Defines entity attributes and relations.
|
40
|
+
# schema do
|
41
|
+
# attributes # ...
|
42
|
+
# belongs_to # ...
|
43
|
+
# has_many # ...
|
44
|
+
# end
|
45
|
+
def schema
|
46
|
+
@available_attributes = []
|
47
|
+
|
48
|
+
yield
|
49
|
+
|
50
|
+
add_id_attribute
|
51
|
+
define_methods
|
52
|
+
|
53
|
+
define_method(:initialize) do |**args, &block|
|
54
|
+
assign_values(args)
|
55
|
+
assign_belongs_to(args)
|
56
|
+
assign_has_many
|
57
|
+
|
58
|
+
block&.call(self)
|
59
|
+
|
60
|
+
freeze_it
|
61
|
+
end
|
62
|
+
|
63
|
+
define_method(:==) do |other|
|
64
|
+
cmp_with(other)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Used in _schema_ block to define attributes available for the entity.
|
69
|
+
def attributes(*list)
|
70
|
+
list.each { |attr| @available_attributes << attr }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Used in _schema_ block to define belongs-to type of relation.
|
74
|
+
def belongs_to(*list)
|
75
|
+
@available_belongs_tos = []
|
76
|
+
list.each do |attr|
|
77
|
+
@available_belongs_tos << attr
|
78
|
+
|
79
|
+
relation_id_attr = "#{attr}_id".to_sym
|
80
|
+
@available_attributes << relation_id_attr unless @available_attributes.include?(relation_id_attr)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Used in _schema_ block to define has-many type of relation.
|
85
|
+
# rubocop:disable Naming/PredicateName
|
86
|
+
def has_many(*list)
|
87
|
+
@available_has_manys = []
|
88
|
+
list.each { |attr| @available_has_manys << attr }
|
89
|
+
end
|
90
|
+
# rubocop:enable Naming/PredicateName
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def add_id_attribute
|
95
|
+
@available_attributes.prepend(:id) unless @available_attributes.include?(:id)
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_methods
|
99
|
+
available_attributes.each do |attr|
|
100
|
+
define_accessor_for(attr)
|
101
|
+
end
|
102
|
+
|
103
|
+
available_belongs_tos.each do |attr|
|
104
|
+
define_accessor_for(attr)
|
105
|
+
|
106
|
+
define_method("#{attr}=") do |obj|
|
107
|
+
instance_variable_set("@#{attr}_id", Optiomist.some(obj&.id))
|
108
|
+
instance_variable_set("@#{attr}", Optiomist.some(obj))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
available_has_manys.each do |attr|
|
113
|
+
define_method(attr) do
|
114
|
+
instance_variable_get("@#{attr}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def define_accessor_for(attr)
|
120
|
+
define_method(attr) do
|
121
|
+
option = instance_variable_get("@#{attr}")
|
122
|
+
option.value unless option.none?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# List of attributes with an assigned value.
|
128
|
+
def defined_attributes
|
129
|
+
self.class.available_attributes.select do |attr|
|
130
|
+
instance_variable_get("@#{attr}").some?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# A hash where keys are attribute symbols and values are their values.
|
135
|
+
# Only defined attributes are included.
|
136
|
+
def defined_attributes_with_values
|
137
|
+
{}.tap do |h|
|
138
|
+
self.class.available_attributes.each do |attr|
|
139
|
+
option = instance_variable_get("@#{attr}")
|
140
|
+
h[attr.to_sym] = option.value unless option.none?
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def freeze_it
|
148
|
+
freeze
|
149
|
+
self.class.available_has_manys.each do |attr|
|
150
|
+
instance_variable_get("@#{attr}").freeze
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def assign_values(args)
|
155
|
+
self.class.available_attributes.each do |attr|
|
156
|
+
instance_variable_set("@#{attr}", args.key?(attr) ? Optiomist.some(args[attr]) : Optiomist.none)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def assign_belongs_to(args)
|
161
|
+
self.class.available_belongs_tos.each do |attr|
|
162
|
+
if args.key?(attr)
|
163
|
+
send("#{attr}=", args[attr])
|
164
|
+
else
|
165
|
+
instance_variable_set("@#{attr}", Optiomist.none)
|
166
|
+
instance_variable_set("@#{attr}_id", Optiomist.none) unless instance_variable_get("@#{attr}_id").some?
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def assign_has_many
|
172
|
+
self.class.available_has_manys.each do |attr|
|
173
|
+
instance_variable_set("@#{attr}", [])
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# They are considered equal if their ids are equal.
|
178
|
+
# If both ids are nil, their defined attributes are compared for equality.
|
179
|
+
def cmp_with(other)
|
180
|
+
instance_of?(other.class) && (
|
181
|
+
(!id.nil? && !other.id.nil? && id == other.id) ||
|
182
|
+
defined_attributes_with_values == other.defined_attributes_with_values
|
183
|
+
)
|
184
|
+
end
|
185
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: entitainer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michał Radmacher
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: optiomist
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.3
|
27
|
+
description:
|
28
|
+
email: michal@radmacher.pl
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.md
|
33
|
+
- LICENSE
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- lib/entitainer.rb
|
38
|
+
homepage: https://github.com/mradmacher/entitainer
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata:
|
42
|
+
rubygems_mfa_required: 'true'
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 2.7.0
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubygems_version: 3.4.10
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: Immutable representations of database entities
|
62
|
+
test_files: []
|