castkit-activerecord 0.1.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.
- checksums.yaml +7 -0
- data/.idea/workspace.xml +307 -0
- data/.rspec +3 -0
- data/.rspec_status +19 -0
- data/.rubocop.yml +41 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +98 -0
- data/Rakefile +12 -0
- data/castkit-activerecord.gemspec +37 -0
- data/lib/castkit/active_record/attribute_assigner.rb +134 -0
- data/lib/castkit/active_record/error.rb +49 -0
- data/lib/castkit/active_record/extensions.rb +105 -0
- data/lib/castkit/active_record/serialization.rb +85 -0
- data/lib/castkit/active_record/version.rb +7 -0
- data/lib/castkit/active_record.rb +88 -0
- data/lib/castkit-activerecord.rb +10 -0
- data/sig/castkit/activerecord.rbs +6 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: caef0ebc2c417ad92c3aaf2f4a1bddfad959ca9b05824068645b20499939d1dd
|
4
|
+
data.tar.gz: d90987a60c163bcab570821d8b99ebb9d701023d599ea4c7e8ec12b6cf809c2f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aa44866667adcf3d9ae25b26b2d5facec9d77796420370ad68f372e36e412ca93087ae4fcda76885ef727d6c0946ea2cca64e99298db2a91727d99cf89de90b7
|
7
|
+
data.tar.gz: 3d28d5f88764505dbb2238823df1f3172b41b7168dc21c1d767261c02e0707014fa15aaec8b8b91e1f33e60dfb77e226c0d049d0974fa517251f6aca296e979f
|
data/.idea/workspace.xml
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project version="4">
|
3
|
+
<component name="AutoImportSettings">
|
4
|
+
<option name="autoReloadType" value="SELECTIVE" />
|
5
|
+
</component>
|
6
|
+
<component name="ChangeListManager">
|
7
|
+
<list default="true" id="68f57f34-e50a-475a-a712-14af61f4ccd1" name="Changes" comment="">
|
8
|
+
<change afterPath="$PROJECT_DIR$/.github/workflows/main.yml" afterDir="false" />
|
9
|
+
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
10
|
+
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
11
|
+
<change afterPath="$PROJECT_DIR$/.rspec" afterDir="false" />
|
12
|
+
<change afterPath="$PROJECT_DIR$/.rubocop.yml" afterDir="false" />
|
13
|
+
<change afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
|
14
|
+
<change afterPath="$PROJECT_DIR$/CODE_OF_CONDUCT.md" afterDir="false" />
|
15
|
+
<change afterPath="$PROJECT_DIR$/Gemfile" afterDir="false" />
|
16
|
+
<change afterPath="$PROJECT_DIR$/LICENSE.txt" afterDir="false" />
|
17
|
+
<change afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
18
|
+
<change afterPath="$PROJECT_DIR$/Rakefile" afterDir="false" />
|
19
|
+
<change afterPath="$PROJECT_DIR$/bin/console" afterDir="false" />
|
20
|
+
<change afterPath="$PROJECT_DIR$/bin/setup" afterDir="false" />
|
21
|
+
<change afterPath="$PROJECT_DIR$/castkit-activerecord.gemspec" afterDir="false" />
|
22
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit-activerecord.rb" afterDir="false" />
|
23
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit/active_record.rb" afterDir="false" />
|
24
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit/active_record/attribute_assigner.rb" afterDir="false" />
|
25
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit/active_record/error.rb" afterDir="false" />
|
26
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit/active_record/extensions.rb" afterDir="false" />
|
27
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit/active_record/serialization.rb" afterDir="false" />
|
28
|
+
<change afterPath="$PROJECT_DIR$/lib/castkit/active_record/version.rb" afterDir="false" />
|
29
|
+
<change afterPath="$PROJECT_DIR$/sig/castkit/activerecord.rbs" afterDir="false" />
|
30
|
+
<change afterPath="$PROJECT_DIR$/spec/castkit/active_record/attribute_assigner_spec.rb" afterDir="false" />
|
31
|
+
<change afterPath="$PROJECT_DIR$/spec/castkit/active_record/extensions_spec.rb" afterDir="false" />
|
32
|
+
<change afterPath="$PROJECT_DIR$/spec/castkit/activerecord_spec.rb" afterDir="false" />
|
33
|
+
<change afterPath="$PROJECT_DIR$/spec/spec_helper.rb" afterDir="false" />
|
34
|
+
</list>
|
35
|
+
<option name="SHOW_DIALOG" value="false" />
|
36
|
+
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
37
|
+
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
38
|
+
<option name="LAST_RESOLUTION" value="IGNORE" />
|
39
|
+
</component>
|
40
|
+
<component name="FileTemplateManagerImpl">
|
41
|
+
<option name="RECENT_TEMPLATES">
|
42
|
+
<list>
|
43
|
+
<option value="Ruby Module" />
|
44
|
+
<option value="Ruby File" />
|
45
|
+
<option value="Ruby Class" />
|
46
|
+
</list>
|
47
|
+
</option>
|
48
|
+
</component>
|
49
|
+
<component name="Git.Settings">
|
50
|
+
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
51
|
+
</component>
|
52
|
+
<component name="ProjectColorInfo"><![CDATA[{
|
53
|
+
"associatedIndex": 3
|
54
|
+
}]]></component>
|
55
|
+
<component name="ProjectId" id="2veSb7YQufWlg9O480fu0n2Wigc" />
|
56
|
+
<component name="ProjectViewState">
|
57
|
+
<option name="autoscrollFromSource" value="true" />
|
58
|
+
<option name="hideEmptyMiddlePackages" value="true" />
|
59
|
+
<option name="showLibraryContents" value="true" />
|
60
|
+
</component>
|
61
|
+
<component name="PropertiesComponent"><![CDATA[{
|
62
|
+
"keyToString": {
|
63
|
+
"RSpec.All specs in spec: castkit-activerecord.executor": "Run",
|
64
|
+
"RSpec.Castkit::ActiveRecord.executor": "Run",
|
65
|
+
"RSpec.Castkit::ActiveRecord::AttributeAssigner.executor": "Run",
|
66
|
+
"RSpec.Castkit::ActiveRecord::Extensions.executor": "Run",
|
67
|
+
"RSpec.Castkit::ActiveRecord::UpdateModel.executor": "Run",
|
68
|
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
69
|
+
"git-widget-placeholder": "main",
|
70
|
+
"node.js.detected.package.eslint": "true",
|
71
|
+
"node.js.detected.package.tslint": "true",
|
72
|
+
"node.js.selected.package.eslint": "(autodetect)",
|
73
|
+
"node.js.selected.package.tslint": "(autodetect)",
|
74
|
+
"nodejs_package_manager_path": "npm",
|
75
|
+
"ruby.structure.view.model.defaults.configured": "true",
|
76
|
+
"vue.rearranger.settings.migration": "true"
|
77
|
+
}
|
78
|
+
}]]></component>
|
79
|
+
<component name="RecentsManager">
|
80
|
+
<key name="MoveFile.RECENT_KEYS">
|
81
|
+
<recent name="$PROJECT_DIR$/lib/castkit" />
|
82
|
+
<recent name="$PROJECT_DIR$/lib" />
|
83
|
+
<recent name="$PROJECT_DIR$/lib/castkit/activerecord" />
|
84
|
+
</key>
|
85
|
+
</component>
|
86
|
+
<component name="RunManager" selected="RSpec.All specs in spec: castkit-activerecord">
|
87
|
+
<configuration name="All specs in spec: castkit-activerecord" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true">
|
88
|
+
<module name="castkit-activerecord" />
|
89
|
+
<predefined_log_file enabled="true" id="RUBY_RSPEC" />
|
90
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
|
91
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
|
92
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
|
93
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
|
94
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
|
95
|
+
<envs>
|
96
|
+
<env name="JRUBY_OPTS" value="-X+O" />
|
97
|
+
</envs>
|
98
|
+
<EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
|
99
|
+
<EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
|
100
|
+
<COVERAGE_PATTERN ENABLED="true">
|
101
|
+
<PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
|
102
|
+
</COVERAGE_PATTERN>
|
103
|
+
</EXTENSION>
|
104
|
+
<EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
|
105
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="$MODULE_DIR$/spec" />
|
106
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="" />
|
107
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
|
108
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
|
109
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
|
110
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="" />
|
111
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
|
112
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
|
113
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="ALL_IN_FOLDER" />
|
114
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
|
115
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
|
116
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
|
117
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
|
118
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
|
119
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
|
120
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
|
121
|
+
<method v="2" />
|
122
|
+
</configuration>
|
123
|
+
<configuration name="Castkit::ActiveRecord" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
|
124
|
+
<module name="castkit-activerecord" />
|
125
|
+
<predefined_log_file enabled="true" id="RUBY_RSPEC" />
|
126
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
|
127
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
|
128
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
|
129
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
|
130
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
|
131
|
+
<envs>
|
132
|
+
<env name="JRUBY_OPTS" value="-X+O" />
|
133
|
+
</envs>
|
134
|
+
<EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
|
135
|
+
<EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
|
136
|
+
<COVERAGE_PATTERN ENABLED="true">
|
137
|
+
<PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
|
138
|
+
</COVERAGE_PATTERN>
|
139
|
+
</EXTENSION>
|
140
|
+
<EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
|
141
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
|
142
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/castkit/active_record/core_spec.rb" />
|
143
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
|
144
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
|
145
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
|
146
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="Castkit::ActiveRecord" />
|
147
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
|
148
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
|
149
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
|
150
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
|
151
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
|
152
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
|
153
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
|
154
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
|
155
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
|
156
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
|
157
|
+
<method v="2" />
|
158
|
+
</configuration>
|
159
|
+
<configuration name="Castkit::ActiveRecord::AttributeAssigner" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
|
160
|
+
<module name="castkit-activerecord" />
|
161
|
+
<predefined_log_file enabled="true" id="RUBY_RSPEC" />
|
162
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
|
163
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
|
164
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
|
165
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
|
166
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
|
167
|
+
<envs>
|
168
|
+
<env name="JRUBY_OPTS" value="-X+O" />
|
169
|
+
</envs>
|
170
|
+
<EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
|
171
|
+
<EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
|
172
|
+
<COVERAGE_PATTERN ENABLED="true">
|
173
|
+
<PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
|
174
|
+
</COVERAGE_PATTERN>
|
175
|
+
</EXTENSION>
|
176
|
+
<EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
|
177
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
|
178
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/castkit/active_record/attribute_assigner_spec.rb" />
|
179
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
|
180
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
|
181
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
|
182
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="Castkit::ActiveRecord::AttributeAssigner" />
|
183
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
|
184
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
|
185
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
|
186
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
|
187
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
|
188
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
|
189
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
|
190
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
|
191
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
|
192
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
|
193
|
+
<method v="2" />
|
194
|
+
</configuration>
|
195
|
+
<configuration name="Castkit::ActiveRecord::Extensions" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
|
196
|
+
<module name="castkit-activerecord" />
|
197
|
+
<predefined_log_file enabled="true" id="RUBY_RSPEC" />
|
198
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
|
199
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
|
200
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
|
201
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
|
202
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
|
203
|
+
<envs>
|
204
|
+
<env name="JRUBY_OPTS" value="-X+O" />
|
205
|
+
</envs>
|
206
|
+
<EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
|
207
|
+
<EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
|
208
|
+
<COVERAGE_PATTERN ENABLED="true">
|
209
|
+
<PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
|
210
|
+
</COVERAGE_PATTERN>
|
211
|
+
</EXTENSION>
|
212
|
+
<EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
|
213
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
|
214
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/castkit/active_record/extensions_spec.rb" />
|
215
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
|
216
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
|
217
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
|
218
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="Castkit::ActiveRecord::Extensions" />
|
219
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
|
220
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
|
221
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
|
222
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
|
223
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
|
224
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
|
225
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
|
226
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
|
227
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
|
228
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
|
229
|
+
<method v="2" />
|
230
|
+
</configuration>
|
231
|
+
<configuration name="Castkit::ActiveRecord::UpdateModel" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
|
232
|
+
<module name="castkit-activerecord" />
|
233
|
+
<predefined_log_file enabled="true" id="RUBY_RSPEC" />
|
234
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
|
235
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
|
236
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
|
237
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
|
238
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
|
239
|
+
<envs>
|
240
|
+
<env name="JRUBY_OPTS" value="-X+O" />
|
241
|
+
</envs>
|
242
|
+
<EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
|
243
|
+
<EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
|
244
|
+
<COVERAGE_PATTERN ENABLED="true">
|
245
|
+
<PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
|
246
|
+
</COVERAGE_PATTERN>
|
247
|
+
</EXTENSION>
|
248
|
+
<EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
|
249
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
|
250
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/castkit/active_record/update_model_spec.rb" />
|
251
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
|
252
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
|
253
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
|
254
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="Castkit::ActiveRecord::UpdateModel" />
|
255
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
|
256
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
|
257
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
|
258
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
|
259
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
|
260
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
|
261
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
|
262
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
|
263
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
|
264
|
+
<RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
|
265
|
+
<method v="2" />
|
266
|
+
</configuration>
|
267
|
+
<recent_temporary>
|
268
|
+
<list>
|
269
|
+
<item itemvalue="RSpec.All specs in spec: castkit-activerecord" />
|
270
|
+
<item itemvalue="RSpec.Castkit::ActiveRecord::AttributeAssigner" />
|
271
|
+
<item itemvalue="RSpec.Castkit::ActiveRecord" />
|
272
|
+
<item itemvalue="RSpec.Castkit::ActiveRecord::Extensions" />
|
273
|
+
<item itemvalue="RSpec.Castkit::ActiveRecord::UpdateModel" />
|
274
|
+
</list>
|
275
|
+
</recent_temporary>
|
276
|
+
</component>
|
277
|
+
<component name="SharedIndexes">
|
278
|
+
<attachedChunks>
|
279
|
+
<set>
|
280
|
+
<option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-RM-243.26053.19" />
|
281
|
+
</set>
|
282
|
+
</attachedChunks>
|
283
|
+
</component>
|
284
|
+
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
285
|
+
<component name="SpringUtil" SPRING_PRE_LOADER_OPTION="true" RAKE_SPRING_PRE_LOADER_OPTION="true" RAILS_SPRING_PRE_LOADER_OPTION="true" />
|
286
|
+
<component name="TaskManager">
|
287
|
+
<task active="true" id="Default" summary="Default task">
|
288
|
+
<changelist id="68f57f34-e50a-475a-a712-14af61f4ccd1" name="Changes" comment="" />
|
289
|
+
<created>1744504114865</created>
|
290
|
+
<option name="number" value="Default" />
|
291
|
+
<option name="presentableId" value="Default" />
|
292
|
+
<updated>1744504114865</updated>
|
293
|
+
<workItem from="1744504116069" duration="29224000" />
|
294
|
+
</task>
|
295
|
+
<servers />
|
296
|
+
</component>
|
297
|
+
<component name="TypeScriptGeneratedFilesManager">
|
298
|
+
<option name="version" value="3" />
|
299
|
+
</component>
|
300
|
+
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
301
|
+
<SUITE FILE_PATH="coverage/castkit_activerecord@Castkit__ActiveRecord__AttributeAssigner.rcov" NAME="Castkit::ActiveRecord::AttributeAssigner Coverage Results" MODIFIED="1744546387147" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="castkit-activerecord" />
|
302
|
+
<SUITE FILE_PATH="coverage/castkit_activerecord@All_specs_in_spec__castkit_activerecord.rcov" NAME="All specs in spec: castkit-activerecord Coverage Results" MODIFIED="1744547818646" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="castkit-activerecord" />
|
303
|
+
<SUITE FILE_PATH="coverage/castkit_activerecord@Castkit__ActiveRecord__Extensions.rcov" NAME="Castkit::ActiveRecord::Extensions Coverage Results" MODIFIED="1744513004728" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="castkit-activerecord" />
|
304
|
+
<SUITE FILE_PATH="coverage/castkit_activerecord@Castkit__ActiveRecord.rcov" NAME="Castkit::ActiveRecord Coverage Results" MODIFIED="1744533156469" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="castkit-activerecord" />
|
305
|
+
<SUITE FILE_PATH="coverage/castkit_activerecord@Castkit__ActiveRecord__UpdateModel.rcov" NAME="Castkit::ActiveRecord::UpdateModel Coverage Results" MODIFIED="1744512870206" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="castkit-activerecord" />
|
306
|
+
</component>
|
307
|
+
</project>
|
data/.rspec
ADDED
data/.rspec_status
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
-------------------------------------------------------------- | ------ | --------------- |
|
3
|
+
./spec/castkit/active_record/attribute_assigner_spec.rb[1:1:1] | passed | 0.00053 seconds |
|
4
|
+
./spec/castkit/active_record/attribute_assigner_spec.rb[1:1:2] | passed | 0.00012 seconds |
|
5
|
+
./spec/castkit/active_record/attribute_assigner_spec.rb[1:2:1] | passed | 0.0006 seconds |
|
6
|
+
./spec/castkit/active_record/attribute_assigner_spec.rb[1:2:2] | passed | 0.00008 seconds |
|
7
|
+
./spec/castkit/active_record/attribute_assigner_spec.rb[1:3:1] | passed | 0.00042 seconds |
|
8
|
+
./spec/castkit/active_record/attribute_assigner_spec.rb[1:3:2] | passed | 0.0001 seconds |
|
9
|
+
./spec/castkit/active_record/extensions_spec.rb[1:1:1] | passed | 0.00061 seconds |
|
10
|
+
./spec/castkit/active_record/extensions_spec.rb[1:1:2] | passed | 0.00005 seconds |
|
11
|
+
./spec/castkit/active_record/extensions_spec.rb[1:1:3] | passed | 0.00112 seconds |
|
12
|
+
./spec/castkit/active_record/extensions_spec.rb[1:2:1] | passed | 0.08415 seconds |
|
13
|
+
./spec/castkit/active_record/extensions_spec.rb[1:3:1] | passed | 0.00019 seconds |
|
14
|
+
./spec/castkit/activerecord_spec.rb[1:1:1] | passed | 0.00013 seconds |
|
15
|
+
./spec/castkit/activerecord_spec.rb[1:1:2] | passed | 0.00012 seconds |
|
16
|
+
./spec/castkit/activerecord_spec.rb[1:1:3] | passed | 0.00006 seconds |
|
17
|
+
./spec/castkit/activerecord_spec.rb[1:2:1] | passed | 0.00012 seconds |
|
18
|
+
./spec/castkit/activerecord_spec.rb[1:3:1] | passed | 0.00014 seconds |
|
19
|
+
./spec/castkit/activerecord_spec.rb[1:3:2] | passed | 0.00008 seconds |
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.7.0
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: true
|
5
|
+
|
6
|
+
Style/Documentation:
|
7
|
+
Exclude:
|
8
|
+
- spec/castkit/**/*.rb
|
9
|
+
|
10
|
+
Style/StringLiterals:
|
11
|
+
Enabled: true
|
12
|
+
EnforcedStyle: double_quotes
|
13
|
+
|
14
|
+
Style/StringLiteralsInInterpolation:
|
15
|
+
Enabled: true
|
16
|
+
EnforcedStyle: double_quotes
|
17
|
+
|
18
|
+
Gemspec/DevelopmentDependencies:
|
19
|
+
EnforcedStyle: gemspec
|
20
|
+
|
21
|
+
Layout/LineLength:
|
22
|
+
Max: 120
|
23
|
+
|
24
|
+
Metrics/ClassLength:
|
25
|
+
Max: 200
|
26
|
+
|
27
|
+
Metrics/BlockLength:
|
28
|
+
Exclude:
|
29
|
+
- castkit-activerecord.gemspec
|
30
|
+
- spec/**/*.rb
|
31
|
+
|
32
|
+
Metrics/MethodLength:
|
33
|
+
Max: 20
|
34
|
+
|
35
|
+
Naming/FileName:
|
36
|
+
Exclude:
|
37
|
+
- lib/castkit-activerecord.rb
|
38
|
+
|
39
|
+
Naming/MemoizedInstanceVariableName:
|
40
|
+
Exclude:
|
41
|
+
- lib/castkit/active_record/extensions.rb
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at bnlucas@outlook.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Nathan Lucas
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Castkit ActiveRecord
|
2
|
+
|
3
|
+
**Castkit::ActiveRecord** is a companion gem to [Castkit](https://rubygems.org/gems/castkit) that bridges your `Castkit::DataObject` classes with ActiveRecord models. It enables conversion to/from models, assignment, and safe update flows for nested objects.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
bundle add castkit-activerecord
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
### Setup
|
14
|
+
|
15
|
+
Include the module in your `Castkit::DataObject` class and associate it with a model:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class UserDto < Castkit::DataObject
|
19
|
+
include Castkit::ActiveRecord
|
20
|
+
model User
|
21
|
+
|
22
|
+
string :name
|
23
|
+
has_many :posts, of: PostDto
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
### Converting a Model to a DTO
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
user_dto = UserDto.from_model(User.find(1))
|
31
|
+
```
|
32
|
+
|
33
|
+
### Converting a DTO to a Model
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
user_model = user_dto.to_model
|
37
|
+
```
|
38
|
+
|
39
|
+
### Updating a Model from a DTO
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
user.update_from_dataobject(user_dto, mode: :merge)
|
43
|
+
```
|
44
|
+
|
45
|
+
Use `update_from_dataobject!` if you want to raise on failure. Pass `ignore: [:field]` to skip certain fields.
|
46
|
+
|
47
|
+
## Nested Support
|
48
|
+
|
49
|
+
Nested `dataobject` and `has_many`/`has_one` relationships are supported:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class PostDto < Castkit::DataObject
|
53
|
+
include Castkit::ActiveRecord
|
54
|
+
model Post
|
55
|
+
|
56
|
+
string :title
|
57
|
+
dataobject :author, UserDto
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
## Opting Out of Attribute Updates
|
62
|
+
|
63
|
+
You can skip certain fields from being assigned during updates:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# This is automatically included
|
67
|
+
class User < ApplicationRecord
|
68
|
+
|
69
|
+
castkit_ignored_on_update :id, :status
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
## Update Modes
|
74
|
+
|
75
|
+
- `:replace` — full object replacement (default)
|
76
|
+
- `:merge` — performs recursive merging via `update_from_dataobject!` on nested models
|
77
|
+
|
78
|
+
## Methods
|
79
|
+
|
80
|
+
### On `Castkit::DataObject`
|
81
|
+
|
82
|
+
- `to_model` → Instantiates a model from the DTO
|
83
|
+
|
84
|
+
### On `ActiveRecord::Base`
|
85
|
+
|
86
|
+
- `to_dataobject(UserDto)`
|
87
|
+
- `update_from_dataobject(dto, mode: :replace, ignore: [])`
|
88
|
+
- `update_from_dataobject!(dto, mode: :replace, ignore: [])`
|
89
|
+
|
90
|
+
## 📃 License
|
91
|
+
|
92
|
+
MIT. See [LICENSE](LICENSE.txt).
|
93
|
+
|
94
|
+
---
|
95
|
+
|
96
|
+
## 🙏 Credits
|
97
|
+
|
98
|
+
Created with ❤️ by [Nathan Lucas](https://github.com/bnlucas)
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/castkit/active_record/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "castkit-activerecord"
|
7
|
+
spec.version = Castkit::ActiveRecord::VERSION
|
8
|
+
spec.authors = ["Nathan Lucas"]
|
9
|
+
spec.email = ["bnlucas@outlook.com"]
|
10
|
+
|
11
|
+
spec.summary = "ActiveRecord integration for Castkit."
|
12
|
+
spec.description = "Provides conversion helpers to serialize and deserialize between ActiveRecord " \
|
13
|
+
"models and Castkit DataObjects. Supports nested DataObjects, eager loading, and " \
|
14
|
+
"ActiveRecord-safe hydration."
|
15
|
+
spec.homepage = "https://github.com/bnlucas/castkit-activerecord"
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.required_ruby_version = ">= 2.7.0"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/bnlucas/castkit-activerecord"
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/bnlucas/castkit-activerecord/blob/main/CHANGELOG.md"
|
22
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
23
|
+
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(File.expand_path(f) == __FILE__) ||
|
27
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_dependency "activerecord", ">= 6.1"
|
36
|
+
spec.add_dependency "castkit", "~> 0.1", ">=0.1.1"
|
37
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module ActiveRecord
|
5
|
+
# Converts a Castkit::DataObject into a hash of assignable attributes
|
6
|
+
# for ActiveRecord model instances.
|
7
|
+
#
|
8
|
+
# This handles nested `dataobject` and `dataobject_collection` types,
|
9
|
+
# and supports recursive conversion using `.to_model` or `.update_from_dataobject!`.
|
10
|
+
class AttributeAssigner
|
11
|
+
# Supported update modes
|
12
|
+
UPDATE_MODES = %i[replace merge].freeze
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Builds assignable attributes for a DataObject, suitable for ActiveRecord models.
|
16
|
+
#
|
17
|
+
# @param dataobject [Castkit::DataObject] the DTO instance
|
18
|
+
# @param model [ActiveRecord::Base, nil] optional target model
|
19
|
+
# @param mode [Symbol] the update mode (:replace or :merge)
|
20
|
+
# @param ignore [Array<Symbol>] additional attribute keys to exclude from assignment
|
21
|
+
# @return [Hash<Symbol, Object>] model-assignable attributes
|
22
|
+
def call(dataobject, model = nil, mode: :replace, ignore: [])
|
23
|
+
new(dataobject).attributes(model, mode: mode, ignore: ignore)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param dataobject [Castkit::DataObject] the source data object to assign from
|
28
|
+
def initialize(dataobject)
|
29
|
+
@dataobject = dataobject
|
30
|
+
@attributes = dataobject.class.attributes
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a filtered and transformed hash of attributes for assignment
|
34
|
+
#
|
35
|
+
# @param model [ActiveRecord::Base, nil] optional model for filtering
|
36
|
+
# @param mode [Symbol] either :replace or :merge
|
37
|
+
# @param ignore [Array<Symbol>] optional attribute keys to exclude
|
38
|
+
# @return [Hash<Symbol, Object>] assignable attributes
|
39
|
+
# @raise [ArgumentError] for invalid mode or assignment failure
|
40
|
+
def attributes(model = nil, mode: :replace, ignore: [])
|
41
|
+
validate_mode!(mode)
|
42
|
+
|
43
|
+
assignable_attributes(model, ignore).each_with_object({}) do |(field, attribute), hash|
|
44
|
+
value = @dataobject.public_send(field)
|
45
|
+
(hash[field] = nil) and next unless value
|
46
|
+
|
47
|
+
target = model.public_send(field) if model
|
48
|
+
hash[field] = assign(attribute, value, target, mode)
|
49
|
+
rescue Castkit::Error
|
50
|
+
raise
|
51
|
+
rescue StandardError => e
|
52
|
+
raise ArgumentError, "Unable to assign attribute #{field}, #{e}"
|
53
|
+
end.freeze
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Validates the provided mode against the list of supported modes, UPDATE_MODES.
|
59
|
+
#
|
60
|
+
# @param mode [Symbol] either :replace or :merge
|
61
|
+
# @raise [ArgumentError] for invalid mode or assignment failure
|
62
|
+
def validate_mode!(mode)
|
63
|
+
mode = mode.to_sym
|
64
|
+
raise ArgumentError, "Unsupported update mode: #{mode.inspect}" unless UPDATE_MODES.include?(mode)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Filters attributes based on readonly/ignored rules
|
68
|
+
#
|
69
|
+
# @param model [ActiveRecord::Base, nil]
|
70
|
+
# @param ignore [Array<Symbol>] explicit keys to skip
|
71
|
+
# @return [Hash<Symbol, Castkit::Attribute>] remaining attributes
|
72
|
+
def assignable_attributes(model = nil, ignore = [])
|
73
|
+
return @attributes unless model
|
74
|
+
|
75
|
+
unassignable = model.class.try(:castkit_ignored_on_update) || model.class.readonly_attributes.to_a
|
76
|
+
all_ignored = (unassignable + ignore).map(&:to_sym)
|
77
|
+
|
78
|
+
@attributes.reject { |key, _| all_ignored.include?(key) }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Applies transformation logic for an individual attribute.
|
82
|
+
#
|
83
|
+
# @param attribute [Castkit::Attribute]
|
84
|
+
# @param value [Object] the DTO value
|
85
|
+
# @param target [Object] the current model value (if merging)
|
86
|
+
# @param mode [Symbol]
|
87
|
+
# @return [Object] the value to assign
|
88
|
+
def assign(attribute, value, target, mode)
|
89
|
+
if attribute.dataobject? && value.respond_to?(:to_model)
|
90
|
+
assign_dataobject(value, target, mode)
|
91
|
+
elsif attribute.dataobject_collection? && value.all? { |v| v.respond_to?(:to_model) }
|
92
|
+
assign_dataobject_collection(value, target, mode)
|
93
|
+
else
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Assigns a nested dataobject to the target model
|
99
|
+
#
|
100
|
+
# @param value [Castkit::DataObject]
|
101
|
+
# @param target [Object]
|
102
|
+
# @param mode [Symbol]
|
103
|
+
# @return [ActiveRecord::Base]
|
104
|
+
def assign_dataobject(value, target, mode)
|
105
|
+
return value.to_model if mode == :replace
|
106
|
+
return value.to_model unless target.respond_to?(:update_from_dataobject!)
|
107
|
+
|
108
|
+
target.update_from_dataobject!(value, mode: :merge)
|
109
|
+
target
|
110
|
+
rescue StandardError
|
111
|
+
warn "Unable to assign attribute #{target.inspect} to #{mode.inspect}"
|
112
|
+
raise
|
113
|
+
end
|
114
|
+
|
115
|
+
# Assigns a collection of nested dataobjects to the target model collection
|
116
|
+
#
|
117
|
+
# @param value [Array<Castkit::DataObject>]
|
118
|
+
# @param target [Array<ActiveRecord::Base>]
|
119
|
+
# @param mode [Symbol]
|
120
|
+
# @return [Array<ActiveRecord::Base>]
|
121
|
+
def assign_dataobject_collection(value, target, mode)
|
122
|
+
return value.map(&:to_model) if mode == :replace
|
123
|
+
|
124
|
+
raise ArgumentError, "expected target to be Enumerable" unless target.is_a?(Enumerable)
|
125
|
+
raise ArgumentError, "size mismatch; expected #{target.size}, got #{value.size}" if value.size != target.size
|
126
|
+
|
127
|
+
value.zip(target).map do |dataobject, model|
|
128
|
+
model.update_from_dataobject!(dataobject, mode: :merge)
|
129
|
+
model
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module ActiveRecord
|
5
|
+
# Base error class for all Castkit ActiveRecord-related exceptions.
|
6
|
+
class Error < Castkit::Error; end
|
7
|
+
|
8
|
+
# Raised when a Castkit::DataObject does not have an associated model defined.
|
9
|
+
#
|
10
|
+
# This typically occurs when calling `from_model`, `to_model`, or `update_model!`
|
11
|
+
# without first declaring a model via `.model SomeModel`.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# class UserDto < Castkit::DataObject
|
15
|
+
# include Castkit::ActiveRecord
|
16
|
+
# # no model defined here
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# UserDto.new.to_model
|
20
|
+
# # => raises Castkit::ActiveRecord::ModelNotDefined
|
21
|
+
class ModelNotDefined < Error
|
22
|
+
# @param klass [Class] the dataobject class missing a model definition
|
23
|
+
def initialize(klass)
|
24
|
+
super("No model defined for #{klass.name}. Did you forget to call model SomeModel?")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Raised when the object passed to `update_model!` is not of the expected model type.
|
29
|
+
#
|
30
|
+
# This ensures that the DataObject only attempts to update the specific model it was
|
31
|
+
# designed to work with.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# class UserDto < Castkit::DataObject
|
35
|
+
# include Castkit::ActiveRecord
|
36
|
+
# model User
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# UserDto.new.from_model(comment_record)
|
40
|
+
# # => raises Castkit::ActiveRecord::InvalidModel
|
41
|
+
class InvalidModel < Error
|
42
|
+
# @param model [Class] the expected model class
|
43
|
+
# @param obj [Object] the object that was passed in
|
44
|
+
def initialize(model, obj)
|
45
|
+
super("Expected instance of #{model.class}, got #{obj.class}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "attribute_assigner"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module ActiveRecord
|
7
|
+
# Adds support for customizing update behavior and ergonomic model → DataObject conversion.
|
8
|
+
#
|
9
|
+
# When included into an ActiveRecord model, this module allows:
|
10
|
+
# - Opting out of assigning certain fields during updates via `castkit_ignored_on_update`
|
11
|
+
# - Converting a model instance to a Castkit::DataObject via `to_dataobject`
|
12
|
+
# - Updating a model instance from a Castkit::DataObject via `update_from_dataobject!`
|
13
|
+
module Extensions
|
14
|
+
def self.included(base)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Class-level methods to extend ActiveRecord models.
|
19
|
+
module ClassMethods
|
20
|
+
# Defines or returns the list of attributes that should not be assigned
|
21
|
+
# when calling `update_from_dataobject!` on an ActiveRecord model.
|
22
|
+
#
|
23
|
+
# This method merges any custom-defined attributes with the model's
|
24
|
+
# `readonly_attributes`, unless explicitly overridden.
|
25
|
+
#
|
26
|
+
# @example Mark `:id` and `:status` as unassignable
|
27
|
+
# class User < ApplicationRecord
|
28
|
+
# castkit_ignored_on_update :id, :status
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @param attributes [Array<Symbol>] optional attributes to ignore
|
32
|
+
# @return [Array<Symbol>] the full list of ignored attributes
|
33
|
+
# @raise [NotImplementedError] if the model doesn't support `.readonly_attributes`
|
34
|
+
def castkit_ignored_on_update(*attributes)
|
35
|
+
unless respond_to?(:readonly_attributes)
|
36
|
+
raise NotImplementedError, "Expected class to respond to `readonly_attributes`"
|
37
|
+
end
|
38
|
+
|
39
|
+
@__castkit_ignored_attributes ||= []
|
40
|
+
|
41
|
+
unless attributes.empty?
|
42
|
+
@__castkit_ignored_attributes = attributes.map(&:to_sym)
|
43
|
+
@__castkit_cached_ignored_attributes = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
unassignable_attributes = (@__castkit_ignored_attributes + readonly_attributes.to_a)
|
47
|
+
@__castkit_cached_ignored_attributes ||= unassignable_attributes.map(&:to_sym).uniq.freeze
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Converts this model instance into a Castkit::DataObject.
|
52
|
+
#
|
53
|
+
# @param klass [Class<Castkit::DataObject>] the target dto class
|
54
|
+
# @return [Castkit::DataObject]
|
55
|
+
# @raise [ArgumentError] if the class is not a valid Castkit::DataObject
|
56
|
+
def to_dataobject(klass)
|
57
|
+
unless Castkit.dataobject?(klass) && klass.respond_to?(:from_model)
|
58
|
+
raise ArgumentError, "#{klass} must include Castkit::ActiveRecord"
|
59
|
+
end
|
60
|
+
|
61
|
+
klass.from_model(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Updates this model instance from a Castkit::DataObject.
|
65
|
+
#
|
66
|
+
# This is the safe variant of `update_from_dataobject!`, returning false
|
67
|
+
# if the update or save fails due to validation errors.
|
68
|
+
#
|
69
|
+
# @param dto [Castkit::DataObject] the source dto
|
70
|
+
# @param mode [Symbol] the update mode (:replace or :merge)
|
71
|
+
# @param ignore [Array<Symbol>] additional attributes to skip
|
72
|
+
# @return [Boolean] whether the update was successful
|
73
|
+
def update_from_dataobject(dto, mode: :replace, ignore: [])
|
74
|
+
update_from_dataobject!(dto, mode: mode, ignore: ignore)
|
75
|
+
true
|
76
|
+
rescue ::ActiveRecord::RecordInvalid, ::ActiveModel::ValidationError
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# Updates this model instance from a Castkit::DataObject.
|
81
|
+
#
|
82
|
+
# This performs a recursive update, including nested DataObjects,
|
83
|
+
# and saves the model after assignment.
|
84
|
+
#
|
85
|
+
# @param dto [Castkit::DataObject] the source dto
|
86
|
+
# @param mode [Symbol] the update mode (:replace or :merge)
|
87
|
+
# @param ignore [Array<Symbol>] additional attribute keys to skip
|
88
|
+
# @return [ActiveRecord::Base] the updated model
|
89
|
+
# @raise [ActiveRecord::RecordInvalid, ActiveModel::ValidationError] if the model fails to save
|
90
|
+
def update_from_dataobject!(dto, mode: :replace, ignore: [])
|
91
|
+
attributes = Castkit::ActiveRecord::AttributeAssigner.call(
|
92
|
+
dto,
|
93
|
+
self,
|
94
|
+
mode: mode,
|
95
|
+
ignore: ignore
|
96
|
+
)
|
97
|
+
|
98
|
+
assign_attributes(attributes)
|
99
|
+
save!
|
100
|
+
|
101
|
+
self
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module ActiveRecord
|
5
|
+
# Provides methods for serializing and deserializing between
|
6
|
+
# ActiveRecord model instances and Castkit::DataObject instances.
|
7
|
+
#
|
8
|
+
# This module supports nested associations and optional eager loading
|
9
|
+
# for relations.
|
10
|
+
module Serialization
|
11
|
+
# Builds a Castkit::DataObject from an ActiveRecord model instance.
|
12
|
+
#
|
13
|
+
# @param obj [ActiveRecord::Base] the model instance to convert
|
14
|
+
# @return [Castkit::DataObject] the hydrated DataObject
|
15
|
+
# @raise [Castkit::ActiveRecord::ModelNotDefined] if no model is defined
|
16
|
+
# @raise [Castkit::ActiveRecord::InvalidModel] if the object is not an instance of the expected model
|
17
|
+
def from_model(obj)
|
18
|
+
ensure_model_type!(obj)
|
19
|
+
from_hash(attributes_from_model(obj))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Builds a collection of DataObjects from an ActiveRecord::Relation or array of model instances.
|
23
|
+
#
|
24
|
+
# Optionally eager loads nested associations.
|
25
|
+
#
|
26
|
+
# @param relation [Enumerable] the model collection
|
27
|
+
# @param eager_load [Boolean] whether to eager load nested associations
|
28
|
+
# @param as [Class<Castkit::DataObject>, nil] an optional DataObject class to use
|
29
|
+
# @return [Array<Castkit::DataObject>] an array of hydrated DataObjects
|
30
|
+
# @raise [ArgumentError] if the relation is not enumerable
|
31
|
+
def from_relation(relation, eager_load: false, as: nil)
|
32
|
+
dataobject = as || self
|
33
|
+
raise ArgumentError, "Expected an Enumerable (e.g. ActiveRecord::Relation)" unless relation.respond_to?(:map)
|
34
|
+
|
35
|
+
relation = relation.includes(*dataobject.nested_associations) if eager_load && relation.respond_to?(:includes)
|
36
|
+
relation.map { |record| from_model(record) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a list of nested association fields defined on the DataObject.
|
40
|
+
#
|
41
|
+
# This is used to support eager loading.
|
42
|
+
#
|
43
|
+
# @return [Array<Symbol>] an array of nested field names
|
44
|
+
def nested_associations
|
45
|
+
attributes.values.select(&:dataobject?).map(&:field)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Extracts a hash of attribute values from a model instance.
|
51
|
+
#
|
52
|
+
# For nested attributes, the method recursively invokes `.from_model`.
|
53
|
+
#
|
54
|
+
# @param obj [ActiveRecord::Base] the model instance
|
55
|
+
# @return [Hash] a hash of normalized attribute values for the DataObject
|
56
|
+
def attributes_from_model(obj)
|
57
|
+
model_attributes = obj.attributes.transform_keys(&:to_sym)
|
58
|
+
|
59
|
+
attributes.each_with_object({}) do |(field, attribute), hash|
|
60
|
+
hash[field] =
|
61
|
+
if attribute.dataobject?
|
62
|
+
field_association(obj, attribute)
|
63
|
+
else
|
64
|
+
model_attributes[field]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Resolves a nested field value from a model association.
|
70
|
+
#
|
71
|
+
# @param obj [ActiveRecord::Base] the model
|
72
|
+
# @param attribute [Castkit::Attribute] the Castkit attribute
|
73
|
+
# @return [Object] the resolved value (DataObject or raw value)
|
74
|
+
def field_association(obj, attribute)
|
75
|
+
association = obj.public_send(attribute.field)
|
76
|
+
|
77
|
+
return if association.nil?
|
78
|
+
return attribute.type.from_model(association) if attribute.dataobject?
|
79
|
+
return association.map { attribute.options[:of].from_model(_1) } if attribute.dataobject_collection?
|
80
|
+
|
81
|
+
association
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "castkit"
|
4
|
+
require_relative "active_record/version"
|
5
|
+
require_relative "active_record/error"
|
6
|
+
require_relative "active_record/extensions"
|
7
|
+
require_relative "active_record/serialization"
|
8
|
+
require_relative "active_record/attribute_assigner"
|
9
|
+
|
10
|
+
module Castkit
|
11
|
+
# Adds ActiveRecord integration to a Castkit::DataObject.
|
12
|
+
#
|
13
|
+
# Includes support for defining a corresponding model class, converting
|
14
|
+
# models to/from DataObjects, and assigning model attributes.
|
15
|
+
#
|
16
|
+
# @example Defining a DataObject for an ActiveRecord model
|
17
|
+
# class UserDto < Castkit::DataObject
|
18
|
+
# include Castkit::ActiveRecord
|
19
|
+
# model User
|
20
|
+
#
|
21
|
+
# string :name
|
22
|
+
# has_many :posts, of: PostDto
|
23
|
+
# end
|
24
|
+
module ActiveRecord
|
25
|
+
# Called when the module is included in a class.
|
26
|
+
#
|
27
|
+
# Extends the class with ActiveRecord-aware relationship DSLs.
|
28
|
+
#
|
29
|
+
# @param base [Class] the including class
|
30
|
+
def self.included(base)
|
31
|
+
base.extend(ClassMethods)
|
32
|
+
|
33
|
+
class << base
|
34
|
+
alias_method :belongs_to, :dataobject
|
35
|
+
alias_method :has_one, :dataobject
|
36
|
+
alias_method :has_many, :array
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Defines class-level behavior for ActiveRecord integration.
|
41
|
+
module ClassMethods
|
42
|
+
include Castkit::ActiveRecord::Serialization
|
43
|
+
|
44
|
+
# Defines or returns the ActiveRecord model class associated with the DataObject.
|
45
|
+
#
|
46
|
+
# @param model [Class, nil] the model to assign
|
47
|
+
# @return [Class] the current model class
|
48
|
+
def model(model = nil)
|
49
|
+
model ? (@model_class = model) : @model_class
|
50
|
+
end
|
51
|
+
|
52
|
+
# Ensures that a model has been defined for the given DataObject class.
|
53
|
+
#
|
54
|
+
# @param klass [Class] the DataObject class (defaults to self)
|
55
|
+
# @raise [Castkit::ActiveRecord::ModelNotDefined] if no model is assigned
|
56
|
+
def ensure_model_defined!(klass = self)
|
57
|
+
raise Castkit::ActiveRecord::ModelNotDefined, klass unless klass.model
|
58
|
+
end
|
59
|
+
|
60
|
+
# Ensures that the given object is an instance of the associated model class.
|
61
|
+
#
|
62
|
+
# @param obj [Object] the object to validate
|
63
|
+
# @param klass [Class] the DataObject class (defaults to self)
|
64
|
+
# @raise [Castkit::ActiveRecord::ModelNotDefined] if no model is assigned
|
65
|
+
# @raise [Castkit::ActiveRecord::InvalidModel] if obj is not an instance of the model
|
66
|
+
def ensure_model_type!(obj, klass = self)
|
67
|
+
ensure_model_defined!(klass)
|
68
|
+
raise Castkit::ActiveRecord::InvalidModel.new(model, obj) unless obj.is_a?(model)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Builds a new instance of the associated ActiveRecord model
|
73
|
+
# from the DataObject’s current values.
|
74
|
+
#
|
75
|
+
# Calls `to_model` on any nested DataObjects if available.
|
76
|
+
#
|
77
|
+
# @return [ActiveRecord::Base] a new model with attributes assigned
|
78
|
+
# @raise [Castkit::ActiveRecord::ModelNotDefined] if no model is defined
|
79
|
+
def to_model
|
80
|
+
self.class.ensure_model_defined!
|
81
|
+
attributes = Castkit::ActiveRecord::AttributeAssigner.call(self)
|
82
|
+
|
83
|
+
model = self.class.model.new
|
84
|
+
model.assign_attributes(attributes)
|
85
|
+
model
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: castkit-activerecord
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Lucas
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-04-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: castkit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.1.1
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.1'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.1.1
|
47
|
+
description: Provides conversion helpers to serialize and deserialize between ActiveRecord
|
48
|
+
models and Castkit DataObjects. Supports nested DataObjects, eager loading, and
|
49
|
+
ActiveRecord-safe hydration.
|
50
|
+
email:
|
51
|
+
- bnlucas@outlook.com
|
52
|
+
executables: []
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- ".idea/workspace.xml"
|
57
|
+
- ".rspec"
|
58
|
+
- ".rspec_status"
|
59
|
+
- ".rubocop.yml"
|
60
|
+
- CHANGELOG.md
|
61
|
+
- CODE_OF_CONDUCT.md
|
62
|
+
- LICENSE.txt
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- castkit-activerecord.gemspec
|
66
|
+
- lib/castkit-activerecord.rb
|
67
|
+
- lib/castkit/active_record.rb
|
68
|
+
- lib/castkit/active_record/attribute_assigner.rb
|
69
|
+
- lib/castkit/active_record/error.rb
|
70
|
+
- lib/castkit/active_record/extensions.rb
|
71
|
+
- lib/castkit/active_record/serialization.rb
|
72
|
+
- lib/castkit/active_record/version.rb
|
73
|
+
- sig/castkit/activerecord.rbs
|
74
|
+
homepage: https://github.com/bnlucas/castkit-activerecord
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata:
|
78
|
+
homepage_uri: https://github.com/bnlucas/castkit-activerecord
|
79
|
+
source_code_uri: https://github.com/bnlucas/castkit-activerecord
|
80
|
+
changelog_uri: https://github.com/bnlucas/castkit-activerecord/blob/main/CHANGELOG.md
|
81
|
+
rubygems_mfa_required: 'true'
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 2.7.0
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.5.4
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: ActiveRecord integration for Castkit.
|
101
|
+
test_files: []
|