entitainer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +73 -0
  4. data/lib/entitainer.rb +185 -0
  5. 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: []