hanami-model 0.6.1 → 0.7.0

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +54 -420
  4. data/hanami-model.gemspec +9 -6
  5. data/lib/hanami/entity.rb +107 -191
  6. data/lib/hanami/entity/schema.rb +236 -0
  7. data/lib/hanami/model.rb +52 -138
  8. data/lib/hanami/model/association.rb +37 -0
  9. data/lib/hanami/model/associations/belongs_to.rb +19 -0
  10. data/lib/hanami/model/associations/dsl.rb +29 -0
  11. data/lib/hanami/model/associations/has_many.rb +200 -0
  12. data/lib/hanami/model/configuration.rb +52 -224
  13. data/lib/hanami/model/configurator.rb +62 -0
  14. data/lib/hanami/model/entity_name.rb +35 -0
  15. data/lib/hanami/model/error.rb +37 -24
  16. data/lib/hanami/model/mapping.rb +29 -35
  17. data/lib/hanami/model/migration.rb +31 -0
  18. data/lib/hanami/model/migrator.rb +111 -88
  19. data/lib/hanami/model/migrator/adapter.rb +39 -16
  20. data/lib/hanami/model/migrator/connection.rb +23 -11
  21. data/lib/hanami/model/migrator/mysql_adapter.rb +38 -17
  22. data/lib/hanami/model/migrator/postgres_adapter.rb +20 -19
  23. data/lib/hanami/model/migrator/sqlite_adapter.rb +9 -8
  24. data/lib/hanami/model/plugins.rb +25 -0
  25. data/lib/hanami/model/plugins/mapping.rb +55 -0
  26. data/lib/hanami/model/plugins/schema.rb +55 -0
  27. data/lib/hanami/model/plugins/timestamps.rb +118 -0
  28. data/lib/hanami/model/relation_name.rb +24 -0
  29. data/lib/hanami/model/sql.rb +161 -0
  30. data/lib/hanami/model/sql/console.rb +41 -0
  31. data/lib/hanami/model/sql/consoles/abstract.rb +33 -0
  32. data/lib/hanami/model/sql/consoles/mysql.rb +63 -0
  33. data/lib/hanami/model/sql/consoles/postgresql.rb +68 -0
  34. data/lib/hanami/model/sql/consoles/sqlite.rb +46 -0
  35. data/lib/hanami/model/sql/entity/schema.rb +125 -0
  36. data/lib/hanami/model/sql/types.rb +95 -0
  37. data/lib/hanami/model/sql/types/schema/coercions.rb +198 -0
  38. data/lib/hanami/model/types.rb +99 -0
  39. data/lib/hanami/model/version.rb +1 -1
  40. data/lib/hanami/repository.rb +287 -723
  41. metadata +77 -40
  42. data/EXAMPLE.md +0 -213
  43. data/lib/hanami/entity/dirty_tracking.rb +0 -74
  44. data/lib/hanami/model/adapters/abstract.rb +0 -281
  45. data/lib/hanami/model/adapters/file_system_adapter.rb +0 -288
  46. data/lib/hanami/model/adapters/implementation.rb +0 -111
  47. data/lib/hanami/model/adapters/memory/collection.rb +0 -132
  48. data/lib/hanami/model/adapters/memory/command.rb +0 -113
  49. data/lib/hanami/model/adapters/memory/query.rb +0 -653
  50. data/lib/hanami/model/adapters/memory_adapter.rb +0 -179
  51. data/lib/hanami/model/adapters/null_adapter.rb +0 -24
  52. data/lib/hanami/model/adapters/sql/collection.rb +0 -287
  53. data/lib/hanami/model/adapters/sql/command.rb +0 -88
  54. data/lib/hanami/model/adapters/sql/console.rb +0 -33
  55. data/lib/hanami/model/adapters/sql/consoles/mysql.rb +0 -49
  56. data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +0 -48
  57. data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +0 -26
  58. data/lib/hanami/model/adapters/sql/query.rb +0 -788
  59. data/lib/hanami/model/adapters/sql_adapter.rb +0 -296
  60. data/lib/hanami/model/coercer.rb +0 -74
  61. data/lib/hanami/model/config/adapter.rb +0 -116
  62. data/lib/hanami/model/config/mapper.rb +0 -45
  63. data/lib/hanami/model/mapper.rb +0 -124
  64. data/lib/hanami/model/mapping/attribute.rb +0 -85
  65. data/lib/hanami/model/mapping/coercers.rb +0 -314
  66. data/lib/hanami/model/mapping/collection.rb +0 -490
  67. data/lib/hanami/model/mapping/collection_coercer.rb +0 -79
data/lib/hanami/model.rb CHANGED
@@ -1,172 +1,86 @@
1
- require 'hanami/model/version'
1
+ require 'rom'
2
+ require 'concurrent'
2
3
  require 'hanami/entity'
3
- require 'hanami/entity/dirty_tracking'
4
4
  require 'hanami/repository'
5
- require 'hanami/model/mapper'
6
- require 'hanami/model/configuration'
7
- require 'hanami/model/error'
8
5
 
9
6
  module Hanami
10
- # Model
7
+ # Hanami persistence
11
8
  #
12
9
  # @since 0.1.0
13
10
  module Model
14
- include Utils::ClassAttribute
11
+ require 'hanami/model/version'
12
+ require 'hanami/model/error'
13
+ require 'hanami/model/configuration'
14
+ require 'hanami/model/configurator'
15
+ require 'hanami/model/mapping'
16
+ require 'hanami/model/plugins'
15
17
 
16
- # Framework configuration
17
- #
18
- # @since 0.2.0
19
18
  # @api private
20
- class_attribute :configuration
21
- self.configuration = Configuration.new
19
+ # @since 0.7.0
20
+ @__repositories__ = Concurrent::Array.new # rubocop:disable Style/VariableNumber
22
21
 
23
- # Configure the framework.
24
- # It yields the given block in the context of the configuration
25
- #
26
- # @param blk [Proc] the configuration block
27
- #
28
- # @since 0.2.0
22
+ class << self
23
+ # @since 0.7.0
24
+ # @api private
25
+ attr_reader :config
26
+
27
+ # @since 0.7.0
28
+ # @api private
29
+ attr_reader :loaded
30
+
31
+ # @since 0.7.0
32
+ # @api private
33
+ alias loaded? loaded
34
+ end
35
+
36
+ # Configure the framework
29
37
  #
30
- # @see Hanami::Model
38
+ # @since 0.1.0
31
39
  #
32
40
  # @example
33
41
  # require 'hanami/model'
34
42
  #
35
43
  # Hanami::Model.configure do
36
- # adapter type: :sql, uri: 'postgres://localhost/database'
37
- #
38
- # mapping do
39
- # collection :users do
40
- # entity User
44
+ # adapter :sql, ENV['DATABASE_URL']
41
45
  #
42
- # attribute :id, Integer
43
- # attribute :name, String
44
- # end
45
- # end
46
+ # migrations 'db/migrations'
47
+ # schema 'db/schema.sql'
46
48
  # end
47
- #
48
- # Adapter MUST follow the convention in which adapter class is inflection of adapter name
49
- # The above example has name :sql, thus derived class will be `Hanami::Model::Adapters::SqlAdapter`
50
- def self.configure(&blk)
51
- configuration.instance_eval(&blk)
49
+ def self.configure(&block)
50
+ @config = Configurator.build(&block)
52
51
  self
53
52
  end
54
53
 
55
- # Load the framework
54
+ # Current configuration
56
55
  #
57
- # @since 0.2.0
58
- # @api private
59
- def self.load!
60
- configuration.load!
56
+ # @since 0.1.0
57
+ def self.configuration
58
+ @configuration ||= Configuration.new(config)
61
59
  end
62
60
 
63
- # Unload the framework
64
- #
65
- # @since 0.2.0
61
+ # @since 0.7.0
66
62
  # @api private
67
- def self.unload!
68
- configuration.unload!
63
+ def self.repositories
64
+ @__repositories__
69
65
  end
70
66
 
71
- # Duplicate Hanami::Model in order to create a new separated instance
72
- # of the framework.
73
- #
74
- # The new instance of the framework will be completely decoupled from the
75
- # original. It will inherit the configuration, but all the changes that
76
- # happen after the duplication, won't be reflected on the other copies.
77
- #
78
- # @return [Module] a copy of Hanami::Model
79
- #
80
- # @since 0.2.0
67
+ # @since 0.7.0
81
68
  # @api private
82
- #
83
- # @example Basic usage
84
- # require 'hanami/model'
85
- #
86
- # module MyApp
87
- # Model = Hanami::Model.dupe
88
- # end
89
- #
90
- # MyApp::Model == Hanami::Model # => false
91
- #
92
- # MyApp::Model.configuration ==
93
- # Hanami::Model.configuration # => false
94
- #
95
- # @example Inheriting configuration
96
- # require 'hanami/model'
97
- #
98
- # Hanami::Model.configure do
99
- # adapter type: :sql, uri: 'sqlite3://uri'
100
- # end
101
- #
102
- # module MyApp
103
- # Model = Hanami::Model.dupe
104
- # end
105
- #
106
- # module MyApi
107
- # Model = Hanami::Model.dupe
108
- # Model.configure do
109
- # adapter type: :sql, uri: 'postgresql://uri'
110
- # end
111
- # end
112
- #
113
- # Hanami::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
114
- # MyApp::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
115
- # MyApi::Model.configuration.adapter_config.uri # => 'postgresql://uri'
116
- def self.dupe
117
- dup.tap do |duplicated|
118
- duplicated.configuration = Configuration.new
119
- end
69
+ def self.container
70
+ raise 'Not loaded' unless loaded?
71
+ @container
120
72
  end
121
73
 
122
- # Duplicate the framework and generate modules for the target application
123
- #
124
- # @param mod [Module] the Ruby namespace of the application
125
- # @param blk [Proc] an optional block to configure the framework
126
- #
127
- # @return [Module] a copy of Hanami::Model
128
- #
129
- @since 0.2.0
130
- #
131
- # @see Hanami::Model#dupe
132
- # @see Hanami::Model::Configuration
133
- #
134
- # @example Basic usage
135
- # require 'hanami/model'
136
- #
137
- # module MyApp
138
- # Model = Hanami::Model.dupe
139
- # end
140
- #
141
- # # It will:
142
- # #
143
- # # 1. Generate MyApp::Model
144
- # # 2. Generate MyApp::Entity
145
- # # 3. Generate MyApp::Repository
146
- #
147
- # MyApp::Model == Hanami::Model # => false
148
- # MyApp::Repository == Hanami::Repository # => false
149
- #
150
- # @example Block usage
151
- # require 'hanami/model'
152
- #
153
- # module MyApp
154
- # Model = Hanami::Model.duplicate(self) do
155
- # adapter type: :memory, uri: 'memory://localhost'
156
- # end
157
- # end
158
- #
159
- # Hanami::Model.configuration.adapter_config # => nil
160
- # MyApp::Model.configuration.adapter_config # => #<Hanami::Model::Config::Adapter:0x007ff0ff0244f8 @type=:memory, @uri="memory://localhost", @class_name="MemoryAdapter">
161
- def self.duplicate(mod, &blk)
162
- dupe.tap do |duplicated|
163
- mod.module_eval %{
164
- Entity = Hanami::Entity.dup
165
- Repository = Hanami::Repository.dup
166
- }
74
+ # @since 0.1.0
75
+ def self.load!(&blk) # rubocop:disable Metrics/AbcSize
76
+ configuration.setup.auto_registration(config.directory.to_s) unless config.directory.nil?
77
+ configuration.instance_eval(&blk) if block_given?
78
+ repositories.each(&:load!)
79
+
80
+ @container = ROM.container(configuration)
81
+ configuration.define_entities_mappings(@container, repositories)
167
82
 
168
- duplicated.configure(&blk) if block_given?
169
- end
83
+ @loaded = true
170
84
  end
171
85
  end
172
86
  end
@@ -0,0 +1,37 @@
1
+ require 'rom-sql'
2
+ require 'hanami/model/associations/has_many'
3
+ require 'hanami/model/associations/belongs_to'
4
+
5
+ module Hanami
6
+ module Model
7
+ # Association factory
8
+ #
9
+ # @since 0.7.0
10
+ # @api private
11
+ class Association
12
+ # Instantiate an association
13
+ #
14
+ # @since 0.7.0
15
+ # @api private
16
+ def self.build(repository, target, subject)
17
+ lookup(repository.root.associations[target])
18
+ .new(repository, repository.root.name.to_sym, target, subject)
19
+ end
20
+
21
+ # Translate ROM SQL associations into Hanami::Model associations
22
+ #
23
+ # @since 0.7.0
24
+ # @api private
25
+ def self.lookup(association)
26
+ case association
27
+ when ROM::SQL::Association::OneToMany
28
+ Associations::HasMany
29
+ when ROM::SQL::Association::ManyToOne
30
+ Associations::BelongsTo
31
+ else
32
+ raise "Unsupported association: #{association}"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ require 'hanami/model/types'
2
+
3
+ module Hanami
4
+ module Model
5
+ module Associations
6
+ # Many-To-One association
7
+ #
8
+ # @since 0.7.0
9
+ # @api private
10
+ class BelongsTo
11
+ # @since 0.7.0
12
+ # @api private
13
+ def self.schema_type(entity)
14
+ Sql::Types::Schema::AssociationType.new(entity)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module Hanami
2
+ module Model
3
+ module Associations
4
+ # Auto-infer relations linked to repository's associations
5
+ #
6
+ # @since 0.7.0
7
+ # @api private
8
+ class Dsl
9
+ # @since 0.7.0
10
+ # @api private
11
+ def initialize(repository, &blk)
12
+ @repository = repository
13
+ instance_eval(&blk)
14
+ end
15
+
16
+ # @since 0.7.0
17
+ # @api private
18
+ def has_many(relation, *)
19
+ @repository.__send__(:relations, relation)
20
+ end
21
+
22
+ # @since 0.7.0
23
+ # @api private
24
+ def belongs_to(*)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,200 @@
1
+ require 'hanami/model/types'
2
+
3
+ module Hanami
4
+ module Model
5
+ module Associations
6
+ # One-To-Many association
7
+ #
8
+ # @since 0.7.0
9
+ # @api private
10
+ class HasMany
11
+ # @since 0.7.0
12
+ # @api private
13
+ def self.schema_type(entity)
14
+ type = Sql::Types::Schema::AssociationType.new(entity)
15
+ Types::Strict::Array.member(type)
16
+ end
17
+
18
+ # @since 0.7.0
19
+ # @api private
20
+ attr_reader :repository
21
+
22
+ # @since 0.7.0
23
+ # @api private
24
+ attr_reader :source
25
+
26
+ # @since 0.7.0
27
+ # @api private
28
+ attr_reader :target
29
+
30
+ # @since 0.7.0
31
+ # @api private
32
+ attr_reader :subject
33
+
34
+ # @since 0.7.0
35
+ # @api private
36
+ attr_reader :scope
37
+
38
+ # @since 0.7.0
39
+ # @api private
40
+ def initialize(repository, source, target, subject, scope = nil)
41
+ @repository = repository
42
+ @source = source
43
+ @target = target
44
+ @subject = subject.to_hash unless subject.nil?
45
+ @scope = scope || _build_scope
46
+ freeze
47
+ end
48
+
49
+ # @since 0.7.0
50
+ # @api private
51
+ def create(data)
52
+ entity.new(
53
+ command(:create, aggregate(target), use: [:timestamps])
54
+ .call(data)
55
+ )
56
+ end
57
+
58
+ # @since 0.7.0
59
+ # @api private
60
+ def add(data)
61
+ command(:create, relation(target), use: [:timestamps])
62
+ .call(associate(data))
63
+ end
64
+
65
+ # @since 0.7.0
66
+ # @api private
67
+ def remove(id)
68
+ target_relation = relation(target)
69
+
70
+ command(:update, target_relation.where(target_relation.primary_key => id), use: [:timestamps])
71
+ .call(unassociate)
72
+ end
73
+
74
+ # @since 0.7.0
75
+ # @api private
76
+ def delete
77
+ scope.delete
78
+ end
79
+
80
+ # @since 0.7.0
81
+ # @api private
82
+ def each(&blk)
83
+ scope.each(&blk)
84
+ end
85
+
86
+ # @since 0.7.0
87
+ # @api private
88
+ def map(&blk)
89
+ to_a.map(&blk)
90
+ end
91
+
92
+ # @since 0.7.0
93
+ # @api private
94
+ def to_a
95
+ scope.to_a
96
+ end
97
+
98
+ # @since 0.7.0
99
+ # @api private
100
+ def where(condition)
101
+ __new__(scope.where(condition))
102
+ end
103
+
104
+ # @since 0.7.0
105
+ # @api private
106
+ def count
107
+ scope.count
108
+ end
109
+
110
+ private
111
+
112
+ # @since 0.7.0
113
+ # @api private
114
+ def command(target, relation, options = {})
115
+ repository.command(target, relation, options)
116
+ end
117
+
118
+ # @since 0.7.0
119
+ # @api private
120
+ def entity
121
+ repository.class.entity
122
+ end
123
+
124
+ # @since 0.7.0
125
+ # @api private
126
+ def relation(name)
127
+ repository.relations[name]
128
+ end
129
+
130
+ # @since 0.7.0
131
+ # @api private
132
+ def aggregate(name)
133
+ repository.aggregate(name)
134
+ end
135
+
136
+ # @since 0.7.0
137
+ # @api private
138
+ def association(name)
139
+ relation(target).associations[name]
140
+ end
141
+
142
+ # @since 0.7.0
143
+ # @api private
144
+ def associate(data)
145
+ relation(source)
146
+ .associations[target]
147
+ .associate(container.relations, data, subject)
148
+ end
149
+
150
+ # @since 0.7.0
151
+ # @api private
152
+ def unassociate
153
+ { foreign_key => nil }
154
+ end
155
+
156
+ # @since 0.7.0
157
+ # @api private
158
+ def container
159
+ repository.container
160
+ end
161
+
162
+ # @since 0.7.0
163
+ # @api private
164
+ def primary_key
165
+ association_keys.first
166
+ end
167
+
168
+ # @since 0.7.0
169
+ # @api private
170
+ def foreign_key
171
+ association_keys.last
172
+ end
173
+
174
+ # Returns primary key and foreign key
175
+ #
176
+ # @since 0.7.0
177
+ # @api private
178
+ def association_keys
179
+ relation(source)
180
+ .associations[target]
181
+ .__send__(:join_key_map, container.relations)
182
+ end
183
+
184
+ # @since 0.7.0
185
+ # @api private
186
+ def _build_scope
187
+ result = relation(target)
188
+ result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
189
+ result.as(Repository::MAPPER_NAME)
190
+ end
191
+
192
+ # @since 0.7.0
193
+ # @api private
194
+ def __new__(new_scope)
195
+ self.class.new(repository, source, target, subject, new_scope)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end