anchormodel 0.1.3 → 0.1.4

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.
@@ -9,88 +9,10 @@ module Anchormodel::ModelMixin
9
9
 
10
10
  class_methods do
11
11
  # Creates an attribute linking to an Anchormodel. The attribute should be
12
- # present in the DB and the column should be named the same as `attribute_name.`
13
- # @param attribute_name [String,Symbol] The name and database column of the attribute
14
- # @param anchormodel_class [Class] Class of the Anchormodel (omit if attribute `:foo_bar` holds a `FooBar`)
15
- # @param optional [Boolean] If true, a presence validation is added to the model.
16
- # @param model_readers [Boolean] If true, the model is given an ActiveRecord::Enum style method `my_model.my_key?` reader for each key in the anchormodel
17
- # @param model_writers [Boolean] If true, the model is given an ActiveRecord::Enum style method `my_model.my_key!` writer for each key in the anchormodel
18
- # @param model_scopes [Boolean] If true, the model is given an ActiveRecord::Enum style scope `MyModel.mykey` for each key in the anchormodel
19
- # @param model_methods [Boolean, NilClass] If non-nil, this mass-assigns and overrides `model_readers`, `model_writers` and `model_scopes`
20
- def belongs_to_anchormodel(attribute_name, anchormodel_class = nil, optional: false, model_readers: true,
21
- model_writers: true, model_scopes: true, model_methods: nil)
22
- anchormodel_class ||= attribute_name.to_s.classify.constantize
23
- attribute_name = attribute_name.to_sym
24
- attribute = Anchormodel::Attribute.new(self, attribute_name, anchormodel_class, optional)
25
-
26
- # Mass configurations if model_methods was specfied
27
- unless model_methods.nil?
28
- model_readers = model_methods
29
- model_writers = model_methods
30
- model_scopes = model_methods
31
- end
32
-
33
- # Register attribute
34
- self.anchormodel_attributes = anchormodel_attributes.merge({ attribute_name => attribute }).freeze
35
-
36
- # Add presence validation if required
37
- unless optional
38
- validates attribute_name, presence: true
39
- end
40
-
41
- # Make casting work
42
- # Define serializer/deserializer
43
- active_model_type_value = Anchormodel::ActiveModelTypeValue.new(attribute)
44
-
45
- # Overwrite reader to force building anchors at every retrieval
46
- define_method(attribute_name.to_s) do
47
- active_model_type_value.deserialize(read_attribute(attribute_name))
48
- end
49
-
50
- # Override writer to fail early when an invalid target value is specified
51
- define_method("#{attribute_name}=") do |new_value|
52
- write_attribute(attribute_name, active_model_type_value.serialize(new_value))
53
- end
54
-
55
- # Supply serializer and deserializer
56
- attribute attribute_name, active_model_type_value
57
-
58
- # Create ActiveRecord::Enum style reader directly in the model if asked to do so
59
- # For a model User with anchormodel Role with keys :admin and :guest, this creates user.admin? and user.guest? (returning true iff role is admin/guest)
60
- if model_readers
61
- anchormodel_class.all.each do |entry|
62
- if respond_to?(:"#{entry.key}?")
63
- fail("Anchormodel reader #{entry.key}? already defined for #{self}, add `model_readers: false` to `belongs_to_anchormodel :#{attribute_name}`.")
64
- end
65
- define_method(:"#{entry.key}?") do
66
- public_send(attribute_name.to_s) == entry
67
- end
68
- end
69
- end
70
-
71
- # Create ActiveRecord::Enum style writer directly in the model if asked to do so
72
- # For a model User with anchormodel Role with keys :admin and :guest, this creates user.admin! and user.guest! (setting the role to admin/guest)
73
- if model_writers
74
- anchormodel_class.all.each do |entry|
75
- if respond_to?(:"#{entry.key}!")
76
- fail("Anchormodel writer #{entry.key}! already defined for #{self}, add `model_writers: false` to `belongs_to_anchormodel :#{attribute_name}`.")
77
- end
78
- define_method(:"#{entry.key}!") do
79
- public_send(:"#{attribute_name}=", entry)
80
- end
81
- end
82
- end
83
-
84
- # Create ActiveRecord::Enum style scope directly in the model class if asked to do so
85
- # For a model User with anchormodel Role with keys :admin and :guest, this creates user.admin! and user.guest! (setting the role to admin/guest)
86
- if model_scopes
87
- anchormodel_class.all.each do |entry|
88
- if respond_to?(entry.key)
89
- fail("Anchormodel scope #{entry.key} already defined for #{self}, add `model_scopes: false` to `belongs_to_anchormodel :#{attribute_name}`.")
90
- end
91
- scope(entry.key, -> { where(attribute_name => entry.key) })
92
- end
93
- end
12
+ # present in the DB and the column should be of type String and named the same as `attribute_name`.
13
+ # @see Anchormodel::Util#install_methods_in_model Parameters
14
+ def belongs_to_anchormodel(*args, **kwargs)
15
+ Anchormodel::Util.install_methods_in_model(self, *args, **kwargs)
94
16
  end
95
17
  end
96
18
  end
@@ -0,0 +1,93 @@
1
+ # @api description
2
+ # A swiss army knife for common functionality
3
+ module Anchormodel::Util
4
+ # Installs an anchormodel attribute in a model class
5
+ # @param model_class [ActiveRecord::Base] Internal only. The model class that the attribute should be installed to.
6
+ # @param attribute_name [String,Symbol] The name and database column of the attribute
7
+ # @param anchormodel_class [Class] Class of the Anchormodel (omit if attribute `:foo_bar` holds a `FooBar`)
8
+ # @param optional [Boolean] If false, a presence validation is added to the model.
9
+ # @param model_readers [Boolean] If true, the model is given an ActiveRecord::Enum style method `my_model.my_key?` reader for each key in the anchormodel
10
+ # @param model_writers [Boolean] If true, the model is given an ActiveRecord::Enum style method `my_model.my_key!` writer for each key in the anchormodel
11
+ # @param model_scopes [Boolean] If true, the model is given an ActiveRecord::Enum style scope `MyModel.mykey` for each key in the anchormodel
12
+ # @param model_methods [Boolean, NilClass] If non-nil, this mass-assigns and overrides `model_readers`, `model_writers` and `model_scopes`
13
+ def self.install_methods_in_model(model_class, attribute_name, anchormodel_class = nil,
14
+ optional: false,
15
+ model_readers: true,
16
+ model_writers: true,
17
+ model_scopes: true,
18
+ model_methods: nil)
19
+
20
+ anchormodel_class ||= attribute_name.to_s.classify.constantize
21
+ attribute_name = attribute_name.to_sym
22
+ attribute = Anchormodel::Attribute.new(self, attribute_name, anchormodel_class, optional)
23
+
24
+ # Mass configurations if model_methods was specfied
25
+ unless model_methods.nil?
26
+ model_readers = model_methods
27
+ model_writers = model_methods
28
+ model_scopes = model_methods
29
+ end
30
+
31
+ # Register attribute
32
+ model_class.anchormodel_attributes = model_class.anchormodel_attributes.merge({ attribute_name => attribute }).freeze
33
+
34
+ # Add presence validation if required
35
+ unless optional
36
+ model_class.validates attribute_name, presence: true
37
+ end
38
+
39
+ # Make casting work
40
+ # Define serializer/deserializer
41
+ active_model_type_value = Anchormodel::ActiveModelTypeValueSingle.new(attribute)
42
+
43
+ # Overwrite reader to force building anchors at every retrieval
44
+ model_class.define_method(attribute_name.to_s) do
45
+ active_model_type_value.deserialize(read_attribute_before_type_cast(attribute_name))
46
+ end
47
+
48
+ # Override writer to fail early when an invalid target value is specified
49
+ model_class.define_method("#{attribute_name}=") do |new_value|
50
+ write_attribute(attribute_name, active_model_type_value.serialize(new_value))
51
+ end
52
+
53
+ # Supply serializer and deserializer
54
+ model_class.attribute attribute_name, active_model_type_value
55
+
56
+ # Create ActiveRecord::Enum style reader directly in the model if asked to do so
57
+ # For a model User with anchormodel Role with keys :admin and :guest, this creates user.admin? and user.guest? (returning true iff role is admin/guest)
58
+ if model_readers
59
+ anchormodel_class.all.each do |entry|
60
+ if model_class.respond_to?(:"#{entry.key}?")
61
+ fail("Anchormodel reader #{entry.key}? already defined for #{self}, add `model_readers: false` to `belongs_to_anchormodel :#{attribute_name}`.")
62
+ end
63
+ model_class.define_method(:"#{entry.key}?") do
64
+ public_send(attribute_name.to_s) == entry
65
+ end
66
+ end
67
+ end
68
+
69
+ # Create ActiveRecord::Enum style writer directly in the model if asked to do so
70
+ # For a model User with anchormodel Role with keys :admin and :guest, this creates user.admin! and user.guest! (setting the role to admin/guest)
71
+ if model_writers
72
+ anchormodel_class.all.each do |entry|
73
+ if model_class.respond_to?(:"#{entry.key}!")
74
+ fail("Anchormodel writer #{entry.key}! already defined for #{self}, add `model_writers: false` to `belongs_to_anchormodel :#{attribute_name}`.")
75
+ end
76
+ model_class.define_method(:"#{entry.key}!") do
77
+ public_send(:"#{attribute_name}=", entry)
78
+ end
79
+ end
80
+ end
81
+
82
+ # Create ActiveRecord::Enum style scope directly in the model class if asked to do so
83
+ # For a model User with anchormodel Role with keys :admin and :guest, this creates user.admin! and user.guest! (setting the role to admin/guest)
84
+ if model_scopes
85
+ anchormodel_class.all.each do |entry|
86
+ if model_class.respond_to?(entry.key)
87
+ fail("Anchormodel scope #{entry.key} already defined for #{self}, add `model_scopes: false` to `belongs_to_anchormodel :#{attribute_name}`.")
88
+ end
89
+ model_class.scope(entry.key, -> { where(attribute_name => entry.key) })
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,11 +1,5 @@
1
1
  class Anchormodel
2
2
  module Version
3
- MAJOR = 0
4
- MINOR = 1
5
- PATCH = 3
6
-
7
- EDGE = false
8
-
9
- LABEL = [MAJOR, MINOR, PATCH, EDGE ? 'edge' : nil].compact.join('.')
3
+ LABEL = (Pathname.new(__FILE__).dirname.dirname.dirname / 'VERSION').read
10
4
  end
11
5
  end
data/lib/anchormodel.rb CHANGED
@@ -77,7 +77,8 @@ class Anchormodel
77
77
  end
78
78
  end
79
79
 
80
- require 'anchormodel/active_model_type_value'
80
+ require 'anchormodel/util'
81
+ require 'anchormodel/active_model_type_value_single'
81
82
  require 'anchormodel/attribute'
82
83
  require 'anchormodel/model_mixin'
83
84
  require 'anchormodel/version'
Binary file
@@ -118,4 +118,23 @@ class UserTest < Minitest::Test
118
118
  u.secondary_role = ''
119
119
  assert_nil u.secondary_role
120
120
  end
121
+
122
+ # Attempting to create a model with an invalid constant name should fail
123
+ def test_invalid_key_update
124
+ assert_raises(RuntimeError) { User.create!(role: :admin, locale: :de, preferred_locale: :invalid) }
125
+ end
126
+
127
+ # Attempting to assign an invalid constant name to a model should fail
128
+ def test_invalid_key_assignment
129
+ assert_raises(RuntimeError) { User.new(role: :invalid) }
130
+ end
131
+
132
+ # An invalid constant name into the DB should raise when reading
133
+ def test_invalid_db_read
134
+ sql = <<~SQL.squish
135
+ INSERT INTO users (role, locale, preferred_locale, created_at, updated_at) VALUES ('invalid', 'de', 'de', 'now', 'now')
136
+ SQL
137
+ ActiveRecord::Base.connection.execute(sql)
138
+ assert_raises(RuntimeError) { User.first.role }
139
+ end
121
140
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anchormodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sandro Kalbermatter
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-19 00:00:00.000000000 Z
11
+ date: 2024-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -150,8 +150,8 @@ dependencies:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
152
  version: 1.6.0
153
- description:
154
- email:
153
+ description:
154
+ email:
155
155
  executables: []
156
156
  extensions: []
157
157
  extra_rdoc_files: []
@@ -165,6 +165,7 @@ files:
165
165
  - LICENSE
166
166
  - README.md
167
167
  - Rakefile
168
+ - VERSION
168
169
  - anchormodel.gemspec
169
170
  - bin/rails
170
171
  - doc/Anchormodel.html
@@ -187,14 +188,16 @@ files:
187
188
  - doc/method_list.html
188
189
  - doc/top-level-namespace.html
189
190
  - lib/anchormodel.rb
190
- - lib/anchormodel/active_model_type_value.rb
191
+ - lib/anchormodel/active_model_type_value_single.rb
191
192
  - lib/anchormodel/attribute.rb
192
193
  - lib/anchormodel/model_mixin.rb
194
+ - lib/anchormodel/util.rb
193
195
  - lib/anchormodel/version.rb
194
196
  - lib/generators/anchormodel/USAGE
195
197
  - lib/generators/anchormodel/anchormodel_generator.rb
196
198
  - lib/generators/anchormodel/templates/anchormodel.rb.erb
197
199
  - logo.svg
200
+ - pkg/anchormodel-0.1.3.gem
198
201
  - test/active_record_model/user_test.rb
199
202
  - test/dummy/.gitignore
200
203
  - test/dummy/Rakefile
@@ -233,7 +236,7 @@ homepage: https://github.com/kalsan/anchormodel
233
236
  licenses:
234
237
  - LGPL-3.0-or-later
235
238
  metadata: {}
236
- post_install_message:
239
+ post_install_message:
237
240
  rdoc_options: []
238
241
  require_paths:
239
242
  - lib
@@ -248,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
251
  - !ruby/object:Gem::Version
249
252
  version: '0'
250
253
  requirements: []
251
- rubygems_version: 3.4.13
252
- signing_key:
254
+ rubygems_version: 3.5.6
255
+ signing_key:
253
256
  specification_version: 4
254
257
  summary: Bringing object-oriented programming to Rails enums
255
258
  test_files: []