anchormodel 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []