multi_model_wizard 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/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +8 -0
- data/README.md +160 -0
- data/Rakefile +8 -0
- data/lib/form_object/base.rb +497 -0
- data/lib/multi_model_wizard/config.rb +46 -0
- data/lib/multi_model_wizard/cookie_store.rb +35 -0
- data/lib/multi_model_wizard/dynamic_validation.rb +48 -0
- data/lib/multi_model_wizard/redis_cookie_store.rb +38 -0
- data/lib/multi_model_wizard/version.rb +5 -0
- data/lib/multi_model_wizard/wizard.rb +102 -0
- data/lib/multi_model_wizard.rb +26 -0
- data/multi_model_wizard.gemspec +37 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a50b7f01a93c84a15b78ce62f4b6f9269c44424f6d72b893c51145ad50cde5b0
|
4
|
+
data.tar.gz: d567b88c92d7b6fcf8d1e1dc45ab546a151c65d52ae3fb1bf62688cefae62f34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 40eed0735fade293059f9501b35b1567da14bd953f27acef37a370a0bccd8bae5c135c32774c80332359b04ad1038cca127739ed26b1a8e11fe79cf1637882ea
|
7
|
+
data.tar.gz: b7644a0d5f212951ce1019d838463cd1dbe852c506a7e2a85433ccfaef6e440279190286891fd25647af5680138cf53050fe42a270ff94bbe1d29fdc4b8345b8
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# MultiModelWizard
|
2
|
+
|
3
|
+
MultiModelWizard is a way to create and update multiple ActiveRecord models using one form object. Creates a smart object for your wizards or forms. Create one form and form object that can update multiple models with ease.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
Add this to your Gemfile
|
8
|
+
```
|
9
|
+
$ gem install multi_model_wizard
|
10
|
+
```
|
11
|
+
|
12
|
+
Then run `bundle install` and you're ready to start
|
13
|
+
|
14
|
+
## Use
|
15
|
+
|
16
|
+
Initialize the gem by creating an initializer file and then configuring your settings:
|
17
|
+
```
|
18
|
+
# config/initializers/multi_model_wizard.rb
|
19
|
+
#
|
20
|
+
MultiModelWizard.configure do |config|
|
21
|
+
config.store = { location: :redis, redis_instance: Redis.current }
|
22
|
+
config.form_key = 'custom_car_wizard'
|
23
|
+
end
|
24
|
+
```
|
25
|
+
The above code snippet is an example configuration. You only need to specify an initializer if you want to change the form key
|
26
|
+
or if you want to use Redis as the storage location.
|
27
|
+
|
28
|
+
Note: If your form is going to be over 4kb then you will have to use Redis. Data larger than 4kb can not be stored in cookies (which is the default configuration).
|
29
|
+
|
30
|
+
Create a new form object that inherits from the base class. Make sure to override the `form_steps`, `create`, and `update`.
|
31
|
+
```
|
32
|
+
# form_objects/custom_vehicle_form.rb
|
33
|
+
#
|
34
|
+
class CustomVehicleForm < FormObject::Base
|
35
|
+
cattr_accessor :form_steps do
|
36
|
+
%i[
|
37
|
+
basic_configuration
|
38
|
+
body
|
39
|
+
engine
|
40
|
+
review
|
41
|
+
].freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
def create
|
45
|
+
created = false
|
46
|
+
begin
|
47
|
+
ActiveRecord::Base.transaction do
|
48
|
+
car = Car.new(attributes_for(Car))
|
49
|
+
car.parts = car_parts
|
50
|
+
car.save!
|
51
|
+
end
|
52
|
+
created = true
|
53
|
+
rescue StandardError => err
|
54
|
+
return created
|
55
|
+
end
|
56
|
+
created
|
57
|
+
end
|
58
|
+
|
59
|
+
def update
|
60
|
+
updated = false
|
61
|
+
begin
|
62
|
+
ActiveRecord::Base.transaction do
|
63
|
+
car = Car.find(car_id)
|
64
|
+
car.attributes = attributes_for(Car)
|
65
|
+
car.parts = car_parts
|
66
|
+
car.save!
|
67
|
+
end
|
68
|
+
updated = true
|
69
|
+
rescue StandardError => err
|
70
|
+
return updated
|
71
|
+
end
|
72
|
+
updated
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
Use form in your controller:
|
78
|
+
|
79
|
+
```
|
80
|
+
# controllers/vehicle_wizard_controller.rb
|
81
|
+
#
|
82
|
+
def set_vehicle_form
|
83
|
+
@form ||= Wizards::FormObjects::CarForm.create_form do |form|
|
84
|
+
form.add_model Manufacturer
|
85
|
+
form.add_model Dealer
|
86
|
+
form.add_multiple_instance_model model: Part, instances: parts
|
87
|
+
form.add_dynamic_model prefix: 'vehicle', model: Vehicle
|
88
|
+
form.add_extra_attributes prefix: 'vehicle', attributes: %i[leather_origin], model: Vehicle
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
before_action :set_form_id
|
93
|
+
|
94
|
+
def set_form_id
|
95
|
+
@form_id = params[:vehicle_id]
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
Setting form_id will allow the gem to differ between a new wizard from (creating new data) and an existing form (editing existing models)
|
100
|
+
Note: The `@form_id` should be set equal to whatever model id you are using in your form route.
|
101
|
+
|
102
|
+
You can now pass `@form` to your form views and start interacting with user input. The attributes of the form are the model attributes prefixed with the model name. Example:
|
103
|
+
```
|
104
|
+
dealer = Dealer.new
|
105
|
+
|
106
|
+
dealer.name
|
107
|
+
# => nil
|
108
|
+
|
109
|
+
@form.dealer_name
|
110
|
+
# => nil
|
111
|
+
```
|
112
|
+
|
113
|
+
In the above example you might have `dealer_name` as an open text field in your form. That attribute would be mapped to the `Dealer` model and get validated using those validations.
|
114
|
+
```
|
115
|
+
# views/vehicle_wizard/basic_configuration.rb
|
116
|
+
#
|
117
|
+
<%= form_for @form, url: my_wizard_path, method: :put do |f| %>
|
118
|
+
<%= f.text_field :dealer_name %>
|
119
|
+
|
120
|
+
<%= link_to 'Back', previous_wizard_path, class: 'btn btn-secondary' %>
|
121
|
+
<%= f.submit 'Next', value: 'Next: Configuration', class: 'btn btn-primary'%>
|
122
|
+
<% end %>
|
123
|
+
```
|
124
|
+
|
125
|
+
Models added with `add_multi_model_instance_model` are different. The form attribute to access these will be the pluralized version of the model name.
|
126
|
+
```
|
127
|
+
@form.parts
|
128
|
+
#=> [{ name: nil, type: nil, size: nil}, { name: nil, type: nil, size: nil}]
|
129
|
+
```
|
130
|
+
|
131
|
+
These will also be mapped to the model and validated using its validators.
|
132
|
+
|
133
|
+
`add_dynamic_model` models initialized with the dynamic attribute method will be referenced using whatever you set as the prefix and then the attribute name.
|
134
|
+
```
|
135
|
+
@form.vehicle_type
|
136
|
+
#=> nil
|
137
|
+
```
|
138
|
+
|
139
|
+
|
140
|
+
## Development
|
141
|
+
|
142
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
143
|
+
|
144
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
145
|
+
|
146
|
+
## Contributing
|
147
|
+
|
148
|
+
### Content
|
149
|
+
|
150
|
+
* Write articles
|
151
|
+
* Recording screencasts
|
152
|
+
* Submit presentations
|
153
|
+
|
154
|
+
Pull requests are welcome! Feel free to submit bugs as well.
|
155
|
+
|
156
|
+
1. Fork it! ( https://github.com/schneems/wicked/fork )
|
157
|
+
2. Create your feature branch: `git checkout -b my-new-feature`
|
158
|
+
3. Commit your changes: `git commit -am 'Add some feature'`
|
159
|
+
4. Push to the branch: `git push origin my-new-feature`
|
160
|
+
5. Create a new Pull Request :D
|
data/Rakefile
ADDED
@@ -0,0 +1,497 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require 'multi_model_wizard/dynamic_validation'
|
5
|
+
|
6
|
+
module FormObject
|
7
|
+
class AttributeNameError < StandardError; end
|
8
|
+
class Base
|
9
|
+
include MultiModelWizard::DynamicValidation
|
10
|
+
include ActiveModel::Model
|
11
|
+
include ActiveModel::AttributeAssignment
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Creates a new instance of the form object with all models and configuration
|
15
|
+
# @note This is how all forms should be instantiated
|
16
|
+
# @param block [Block] this yields to a block with an instance of its self
|
17
|
+
# @return form object [FormObjects::Base]
|
18
|
+
def create_form
|
19
|
+
instance = new
|
20
|
+
yield(instance)
|
21
|
+
instance.send(:init_attributes)
|
22
|
+
instance
|
23
|
+
end
|
24
|
+
|
25
|
+
# Needs to be overridden by child class.
|
26
|
+
# @note This method needs to be overridden with an array of symbols
|
27
|
+
# @return array [Array]
|
28
|
+
def form_steps
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# These are the default atrributes for all form objects
|
34
|
+
ATTRIBUTES = %i[
|
35
|
+
current_step
|
36
|
+
new_form
|
37
|
+
]
|
38
|
+
|
39
|
+
attr_reader :extra_attributes, :models, :dynamic_models, :multiple_instance_models
|
40
|
+
|
41
|
+
# WARNING: Light meta programming
|
42
|
+
# We will create an attr_accessor for all atributes
|
43
|
+
# @note This ATTRIBUTES can be overriden in child classes
|
44
|
+
ATTRIBUTES.each { |attribute| attr_accessor attribute }
|
45
|
+
|
46
|
+
alias new_form? new_form
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@models = []
|
50
|
+
@dynamic_models = []
|
51
|
+
@multiple_instance_models = []
|
52
|
+
@extra_attributes = []
|
53
|
+
@new_form = true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks if the form and its attributes are valid
|
57
|
+
# @note This method is here because the Wicked gem automagically runs this methdo to move to the next step
|
58
|
+
# @note This method needs return a boolean after attempting to create the records
|
59
|
+
# @return boolean [Boolean]
|
60
|
+
def save
|
61
|
+
valid?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add a custom error message and makes the object invalid
|
65
|
+
#
|
66
|
+
def invalidate!(error_msg = nil)
|
67
|
+
errors.add(:associated_model, error_msg) unless error_msg.nil?
|
68
|
+
errors.add(:associated_model, 'could not be properly save')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Boolean method returns if the object is on the first step or not
|
72
|
+
# @return boolean [Boolean]
|
73
|
+
def first_step?
|
74
|
+
return false if current_step.nil?
|
75
|
+
return true unless current_step.to_sym
|
76
|
+
|
77
|
+
form_steps.first == current_step.to_sym
|
78
|
+
end
|
79
|
+
|
80
|
+
# Gets all of the form objects present attributes and returns them in a hash
|
81
|
+
# @note This method will only return a key value pair for attributes that are not nil
|
82
|
+
# @note It ignores the models arrays, errors, etc.
|
83
|
+
# @return hash [ActiveSupport::HashWithIndifferentAccess]
|
84
|
+
def attributes
|
85
|
+
hash = ActiveSupport::HashWithIndifferentAccess.new
|
86
|
+
instance_variables.each_with_object(hash) do |attribute, object|
|
87
|
+
next if %i[@errors @validation_context
|
88
|
+
@models @dynamic_models
|
89
|
+
@multiple_instance_models
|
90
|
+
@extra_attributes].include?(attribute)
|
91
|
+
|
92
|
+
key = attribute.to_s.gsub('@','').to_sym
|
93
|
+
object[key] = self.instance_variable_get(attribute)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns an list of all attribute names as symbols
|
98
|
+
# @return array [Array] of symbol attribute names
|
99
|
+
def attribute_keys
|
100
|
+
ATTRIBUTES
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns all of the attributes for a model
|
104
|
+
# @note If you dont pass a model to extra attributes they will not show up here
|
105
|
+
# @note attributes for model does not work for multiple_instance_models
|
106
|
+
# @param model class [ActiveRecord]
|
107
|
+
# @return hash [Hash]
|
108
|
+
def attributes_for(model)
|
109
|
+
model_is_activerecord?(model)
|
110
|
+
|
111
|
+
hash = ActiveSupport::HashWithIndifferentAccess.new
|
112
|
+
|
113
|
+
attribute_lookup.each_with_object(hash) do |value, object|
|
114
|
+
lookup = value[1]
|
115
|
+
form_attribute = value[0]
|
116
|
+
object[lookup[:original_method]] = attributes[form_attribute] if lookup[:model] == model.name
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Takes a hash of key value pairs and assigns those attributes values to its
|
121
|
+
# corresponding methods/instance variables
|
122
|
+
# @note If you give it a key that is not a defined method of the class it will simply move on to the next
|
123
|
+
# @param hash [Hash]
|
124
|
+
# @return self [FormObject] the return value is the instance of the form object with its updated values
|
125
|
+
def set_attributes(attributes_hash)
|
126
|
+
attributes_hash.each do |pair|
|
127
|
+
key = "#{pair[0].to_s}="
|
128
|
+
value = pair[1]
|
129
|
+
self.send(key, value)
|
130
|
+
rescue NoMethodError
|
131
|
+
next
|
132
|
+
end
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# Given n number of atrribute names this method will iterate over each attribute and validate that attribute
|
138
|
+
# using the model that the attribute orignated from to validate it
|
139
|
+
# @note this method uses a special method #validate_attribute_with_message this method comes from
|
140
|
+
# the an DynamicValidation module and is not built in with ActiveRecord
|
141
|
+
# @note this method will add the model errors to your object instance and invalidate it.
|
142
|
+
# The model errors are using the original attribute names
|
143
|
+
# @param symbol names of instance methods [Symbol]
|
144
|
+
# @return boolean [Boolean] this method will return true if all attributes are valid and false if not
|
145
|
+
def validate_attributes(*attributes)
|
146
|
+
raise ArgumentError, 'Attributes must be a Symbol' unless attributes.all? { |x| x.is_a?(Symbol) }
|
147
|
+
|
148
|
+
attributes.map do |single_attr|
|
149
|
+
unless respond_to?(single_attr)
|
150
|
+
raise FormObject::AttributeNameError, "#{single_attr.to_s} is not a valid attribute of this form object"
|
151
|
+
end
|
152
|
+
|
153
|
+
original_attribute = attribute_lookup.dig(single_attr.to_sym, :original_method)
|
154
|
+
attribute_hash = { "#{original_attribute}": send(single_attr) }
|
155
|
+
instance = attribute_lookup.dig(single_attr.to_sym, :model)&.constantize&.new
|
156
|
+
instance&.send("#{original_attribute}=", send(single_attr))
|
157
|
+
next if instance.nil?
|
158
|
+
|
159
|
+
validation = validate_attribute_with_message(attribute_hash, model_instance: instance)
|
160
|
+
if validation.valid.eql?(false)
|
161
|
+
validation.messages.each { |err| errors.add(single_attr.to_sym, err) }
|
162
|
+
end
|
163
|
+
|
164
|
+
validation.valid
|
165
|
+
end.compact.all?(true)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Much like #validate_attributes this method will validate the attributes
|
169
|
+
# of a instance model using the original model
|
170
|
+
# @note this method uses a special method #validate_attribute_with_message this method comes from
|
171
|
+
# the an DynamicValidation module and is not built in with ActiveRecord
|
172
|
+
# @note this method will add the model errors to your object instance and invalidate it.
|
173
|
+
# The model errors are using the original attribute names
|
174
|
+
# @param symbol name of instance method [Symbol]
|
175
|
+
# @return boolean [Boolean] this method will return true if all attributes are valid and false if not
|
176
|
+
def validate_multiple_instance_model(attribute)
|
177
|
+
raise ArgumentError, 'Attribute must be a Symbol' unless attribute.is_a?(Symbol)
|
178
|
+
unless respond_to?(attribute)
|
179
|
+
raise FormObject::AttributeNameError, "#{attribute.to_s} is not a valid attribute of this form object"
|
180
|
+
end
|
181
|
+
|
182
|
+
model_instance = attribute_lookup.dig(attribute.to_sym, :model)&.constantize&.new
|
183
|
+
return nil if model_instance.nil?
|
184
|
+
|
185
|
+
send(attribute).map do |hash_instance|
|
186
|
+
hash_instance.map do |key, value|
|
187
|
+
model_instance&.send("#{key}=", value)
|
188
|
+
|
189
|
+
validation = validate_attribute_with_message({ "#{key}": value }, model_instance: model_instance )
|
190
|
+
if validation.valid.eql?(false)
|
191
|
+
validation.messages.each { |err| errors.add(attribute.to_sym, err) }
|
192
|
+
end
|
193
|
+
validation.valid
|
194
|
+
end
|
195
|
+
end.compact.all?(true)
|
196
|
+
end
|
197
|
+
|
198
|
+
# This method should be used when instantiating a new object. It is used to add extra attributes to the
|
199
|
+
# form object that may not be accessible from the models passed in.
|
200
|
+
# @note to have these attributes validated using the #validate_attributes method you must pass in a model
|
201
|
+
# @note model can be an instance or the class
|
202
|
+
# @note attributes should be an array of symbols
|
203
|
+
# @param prefix is a string that you want to hcae in front of all your extra attributes [String]
|
204
|
+
# @param attributes should be an array of symbols [Array]
|
205
|
+
# @param model class or model instance [ActiveRecord] this is the class that you want these extra attributes to be related to
|
206
|
+
# @return array of all the extra attributes [Array]
|
207
|
+
def add_extra_attributes(prefix: nil, attributes:, model: nil )
|
208
|
+
if prefix.present?
|
209
|
+
raise ArgumentError, 'Prefix must be a String' unless prefix.is_a?(String)
|
210
|
+
end
|
211
|
+
raise ArgumentError, 'All attributes must be Symbols' unless attributes.all? { |x| x.is_a?(Symbol) }
|
212
|
+
model_is_activerecord?(model)
|
213
|
+
|
214
|
+
hash = {
|
215
|
+
prefix: prefix || model_prefix(model),
|
216
|
+
attributes: attributes,
|
217
|
+
model: model
|
218
|
+
}
|
219
|
+
extra_attributes << hash
|
220
|
+
end
|
221
|
+
|
222
|
+
# This method should be used when instantiating a new object. It is used to add dynamic models to the
|
223
|
+
# form object.
|
224
|
+
# Dynamic models are models that share a base class and are of the same family but can vary depending on child class
|
225
|
+
# Example: A Truck model, Racecar model, and a Semi model who all have a base class of Vehicle
|
226
|
+
# This method allows your form to recieve any of these models and keep the UI and method calls the same.
|
227
|
+
# @note model can be an instance or the class
|
228
|
+
# @param prefix is a string that you want to hcae in front of all your extra attributes [String]
|
229
|
+
# @param model class or model instance [ActiveRecord] this is the class that you want these extra attributes to be related to
|
230
|
+
# @return array of all the dynamic models [Array]
|
231
|
+
def add_dynamic_model(prefix:, model:)
|
232
|
+
raise ArgumentError, 'Prefix must be a String' unless prefix.is_a?(String)
|
233
|
+
model_is_activerecord?(model)
|
234
|
+
|
235
|
+
@dynamic_models << { prefix: prefix, model: instance_of_model(model) }
|
236
|
+
end
|
237
|
+
|
238
|
+
# The add_multiple_instance_model is an instance method that is used for adding ActiveRecord models
|
239
|
+
# multiple instance models are models that would be child models in a has_many belongs_to relationship
|
240
|
+
# EXAMPLE: Car has_many parts
|
241
|
+
# In this example the multiple instance would be parts because a car can have an infinte number of parts
|
242
|
+
# @param name of the form object atrribute to retrieve these multiple instances of a model [String]
|
243
|
+
# @param model class or instance [ActiveRecord] this is the same model that the instances should be
|
244
|
+
# @param instances is an array of ActiveRecord models [Array] these are usually the has_many relation instances
|
245
|
+
# @return array of all the multiple instance models models [Array]
|
246
|
+
def add_multiple_instance_model(attribute_name: nil, model:, instances: [])
|
247
|
+
if attribute_name.present?
|
248
|
+
raise ArgumentError, 'Attribute name must be a String' unless attribute_name.is_a?(String)
|
249
|
+
end
|
250
|
+
model_is_activerecord?(model)
|
251
|
+
|
252
|
+
attribute_name = attribute_name || model_prefix(model, pluralize: true)
|
253
|
+
hash = { attribute_name: attribute_name, model: instance_of_model(model), instances: instances }
|
254
|
+
|
255
|
+
@multiple_instance_models << hash
|
256
|
+
end
|
257
|
+
|
258
|
+
# The add_model is an instance method that is used for adding ActiveRecord models
|
259
|
+
# @param prefix is optional and is used to change the prefix of the models attributes [String] the prefix defaults to the model name
|
260
|
+
# @param model class or instance [ActiveRecord] this is the same model that the instances should be
|
261
|
+
# @return array of all the models [Array]
|
262
|
+
def add_model(model, prefix: nil)
|
263
|
+
if prefix.present?
|
264
|
+
raise ArgumentError, 'Prefix must be a String' unless prefix.is_a?(String)
|
265
|
+
end
|
266
|
+
model_is_activerecord?(model)
|
267
|
+
|
268
|
+
hash = { prefix: prefix || model_prefix(model), model: instance_of_model(model) }
|
269
|
+
|
270
|
+
@models << hash
|
271
|
+
end
|
272
|
+
|
273
|
+
# This method is used to turn the attributes of the form into a stringified object that resembles json
|
274
|
+
# @return form atttrubytes as a strigified hash [Hash]
|
275
|
+
def as_json
|
276
|
+
instance_variables.each_with_object({}) do |var, obj|
|
277
|
+
obj[var.to_s.gsub('@','')] = instance_variable_get(var)
|
278
|
+
end.stringify_keys
|
279
|
+
end
|
280
|
+
|
281
|
+
# This method is used to help validate the form object. Use required for step to do step contional validations of attributes
|
282
|
+
# @param step is used to compare the current step [Symbol]
|
283
|
+
# @return a true or false value if the step give is equal to or smaller in the form_steps [Boolean]
|
284
|
+
def required_for_step?(step)
|
285
|
+
# note: this line is specific if using the wicked gem
|
286
|
+
return true if current_step == 'wicked_finish' || current_step.nil?
|
287
|
+
|
288
|
+
form_steps.index(step.to_sym) <= form_steps.index(current_step.to_sym)
|
289
|
+
end
|
290
|
+
|
291
|
+
# Persist is used to update or create all of the models from the form object
|
292
|
+
# @note the create method and update method that this method use will have to manually implemented by the child class
|
293
|
+
# @note the create and update need to return a boolean based on their success or failure to udpate
|
294
|
+
# @return the output of the create or update method [Sybmbol]
|
295
|
+
def persist!
|
296
|
+
new_form ? create : update
|
297
|
+
end
|
298
|
+
|
299
|
+
# Create all of the models from the form object and their realations
|
300
|
+
# @note this should be done in an ActiveRecord transaction block
|
301
|
+
# @return returns true if the transaction was successfule and false if not[Boolean]
|
302
|
+
# EXAMPLE:
|
303
|
+
# def create
|
304
|
+
# created = false
|
305
|
+
# begin
|
306
|
+
# ActiveRecord::Base.transaction do
|
307
|
+
# car = Car.new(attributes_for(Car))
|
308
|
+
# car.parts = car_parts
|
309
|
+
# car.save!
|
310
|
+
# end
|
311
|
+
# created = true
|
312
|
+
# rescue StandardError => err
|
313
|
+
# return created
|
314
|
+
# end
|
315
|
+
# created
|
316
|
+
# end
|
317
|
+
def create
|
318
|
+
true
|
319
|
+
end
|
320
|
+
|
321
|
+
# Update all of the models from the form object and their realations
|
322
|
+
# @note this should be done in an ActiveRecord transaction block
|
323
|
+
# @return returns true if the transaction was successfule and false if not [Boolean]
|
324
|
+
# EXAMPLE:
|
325
|
+
# def update
|
326
|
+
# updated = false
|
327
|
+
# begin
|
328
|
+
# ActiveRecord::Base.transaction do
|
329
|
+
# car = Car.find(car_id)
|
330
|
+
# car.attributes = attributes_for(Car)
|
331
|
+
# car.parts = car_parts
|
332
|
+
# car.save!
|
333
|
+
# end
|
334
|
+
# updated = true
|
335
|
+
# rescue StandardError => err
|
336
|
+
# return updated
|
337
|
+
# end
|
338
|
+
# updated
|
339
|
+
# end
|
340
|
+
def update
|
341
|
+
true
|
342
|
+
end
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
# WARNING: Light meta programming
|
347
|
+
# Used to add attributes to the ATTRIBUTES array and also create an attr_accessor for those attributes
|
348
|
+
# This is used to add model attributes to the form object
|
349
|
+
# @param attribute name [Symbol]
|
350
|
+
# @return method name [Sybmbol]
|
351
|
+
def add_attribute(attribute_name)
|
352
|
+
ATTRIBUTES << attribute_name
|
353
|
+
self.class.send(:attr_accessor, attribute_name)
|
354
|
+
end
|
355
|
+
|
356
|
+
# Used on the last step of a form wizard
|
357
|
+
# @note this model will invalidate the form object is the persist! method does not return true
|
358
|
+
# @return if all models were saved than true will be returned [Boolean]
|
359
|
+
def models_persisted?
|
360
|
+
persist! ? true : errors.add(:associated_model, 'could not be properly save')
|
361
|
+
end
|
362
|
+
|
363
|
+
# Returns all types of models including, dynamic models, multi instance models, etc
|
364
|
+
# @return all models [Array]
|
365
|
+
def all_models
|
366
|
+
@all_models = (models + dynamic_models + multiple_instance_models).uniq
|
367
|
+
end
|
368
|
+
|
369
|
+
# This method is used to make sure the form object has an attr_accessor for all of the models that
|
370
|
+
# were provided. This method is used during the initialization of a new form object instance.
|
371
|
+
def init_attributes
|
372
|
+
# get all model attributes and prefix them
|
373
|
+
all_models.each do |hash|
|
374
|
+
prefix = hash[:prefix]
|
375
|
+
hash[:model].attributes.keys.each do |key|
|
376
|
+
ATTRIBUTES << "#{prefix}_#{key}".to_sym
|
377
|
+
|
378
|
+
# set attribute history
|
379
|
+
set_attribute_history(prefix, key, hash[:model])
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# add extra attributes
|
384
|
+
init_extra_attributes
|
385
|
+
|
386
|
+
init_multiple_instance_attributes
|
387
|
+
|
388
|
+
# set attr_accessor
|
389
|
+
ATTRIBUTES.uniq.each do |attribute|
|
390
|
+
self.class.send(:attr_accessor, attribute)
|
391
|
+
end
|
392
|
+
|
393
|
+
# set any values from the class
|
394
|
+
all_models.each do |hash|
|
395
|
+
prefix = hash[:prefix] || model_prefix(hash[:model])
|
396
|
+
hash[:model].attributes.each do |key, value|
|
397
|
+
self.instance_variable_set("@#{prefix}_#{key}", value)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
@models = []
|
401
|
+
@extra_attributes = []
|
402
|
+
end
|
403
|
+
|
404
|
+
# This method is used to make sure the form object has an attr_accessor for all of the multiple instance attributes
|
405
|
+
# This method is used during the initialization of a new form object instance.
|
406
|
+
def init_multiple_instance_attributes
|
407
|
+
multiple_instance_models.each do |model_hash|
|
408
|
+
add_attribute(model_hash[:attribute_name].to_sym)
|
409
|
+
attribute_lookup.merge!(
|
410
|
+
"#{model_hash[:attribute_name]}": { original_method: nil, model: instance_of_model(model_hash[:model]).class.name }
|
411
|
+
)
|
412
|
+
|
413
|
+
instances = model_hash[:instances].map { |x| ActiveSupport::HashWithIndifferentAccess.new(x.attributes) }
|
414
|
+
self.send("#{model_hash[:attribute_name].to_s}=", instances)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# This method is used to make sure the form object has an attr_accessor for all of the extra attributes
|
419
|
+
# This method is used during the initialization of a new form object instance.
|
420
|
+
def init_extra_attributes
|
421
|
+
@extra_attribute_keys = []
|
422
|
+
extra_attributes.each do |attr_object|
|
423
|
+
attr_object[:attributes].each do |x|
|
424
|
+
if attr_object[:prefix]
|
425
|
+
ATTRIBUTES << "#{attr_object[:prefix].to_s}_#{x.to_s}".to_sym
|
426
|
+
@extra_attribute_keys << "#{attr_object[:prefix].to_s}_#{x.to_s}".to_sym
|
427
|
+
else
|
428
|
+
ATTRIBUTES << x.to_sym
|
429
|
+
@extra_attribute_keys << x.to_sym
|
430
|
+
end
|
431
|
+
|
432
|
+
if attr_object[:model]
|
433
|
+
set_attribute_history(attr_object[:prefix], x.to_s, attr_object[:model])
|
434
|
+
end
|
435
|
+
|
436
|
+
# try to set value for extra atrributes
|
437
|
+
self.instance_variable_set("@#{@extra_attribute_keys.last}", attr_object[:model].try(x.to_s))
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# Set attribute history updates the attrube_lookup object
|
443
|
+
# @param prefix is the attribute prefix [String] the prefix the form_object gave this attribute
|
444
|
+
# @param key is name of the original method from the model [String]
|
445
|
+
# @param model ActiveRecord class of the method [ActiveRecord]
|
446
|
+
def set_attribute_history(prefix=nil, key, model)
|
447
|
+
if prefix
|
448
|
+
attribute_lookup.merge!("#{prefix}_#{key}": { original_method: key, model: instance_of_model(model).class.name })
|
449
|
+
else
|
450
|
+
attribute_lookup.merge!("#{key}": { original_method: key, model: instance_of_model(model).class.name })
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# This method is reponsible for taking an ActiveRecord class and turning it into snake cased prefix
|
455
|
+
# @param pluralize determines if the prefix is going to be plural or not [Boolean]
|
456
|
+
# @param model ActiveRecord class [ActiveRecord]
|
457
|
+
def model_prefix(model, pluralize: false)
|
458
|
+
if pluralize
|
459
|
+
instance_of_model(model).class.name.pluralize.gsub('::','_').underscore
|
460
|
+
else
|
461
|
+
instance_of_model(model).class.name.gsub('::','_').underscore
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# Instance of model gives the DSL the flexibility to have an instance of an ActiveRecord class or the class it self.
|
466
|
+
# This method will take an argument of either an ActiveRecord class instance or class definiton and return the an instance
|
467
|
+
# @param model ActiveRecord class or instance [ActiveRecord]
|
468
|
+
# @return ActiveRecord model instance [ActiveRecord]
|
469
|
+
def instance_of_model(model)
|
470
|
+
model.respond_to?(:new) ? model.new : model
|
471
|
+
end
|
472
|
+
|
473
|
+
def class_for(model)
|
474
|
+
model.respond_to?(:new) ? model : model.class
|
475
|
+
end
|
476
|
+
|
477
|
+
def model_is_activerecord?(model)
|
478
|
+
return if model.nil?
|
479
|
+
|
480
|
+
class_ancestors = class_for(model).ancestors
|
481
|
+
unless class_ancestors.include?(ActiveRecord::Base) || class_ancestors.include?(ActiveRecord::Base)
|
482
|
+
raise ArgumentError, 'Model must be an ActiveRecord descendant'
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
# The attribute lookup method is a hash that has the form object attribute as a key and the history of that atrribute as the value
|
487
|
+
# EXAMPLE:
|
488
|
+
# {
|
489
|
+
# car_color: { original_method: 'color', model: 'Car' }
|
490
|
+
# }
|
491
|
+
#
|
492
|
+
# @return hash [Hash]
|
493
|
+
def attribute_lookup
|
494
|
+
@attribute_lookup ||= {}
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module MultiModelWizard
|
2
|
+
class Config
|
3
|
+
# The form key is what is used a the key in the session cookies
|
4
|
+
# This can be changed in the intitializer file.
|
5
|
+
# This key is also what is used as part of the redis key value pair
|
6
|
+
# if redis is configured.
|
7
|
+
FORM_KEY = 'multi_model_wizard_form'.freeze
|
8
|
+
|
9
|
+
attr_accessor :store, :form_key
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@store = { location: :cookies, redis_instance: nil }
|
13
|
+
@form_key = FORM_KEY
|
14
|
+
end
|
15
|
+
|
16
|
+
# The configured redis instance. This is should be set in the initializer.
|
17
|
+
# A redis instance is only needed if you are going to use redis to store.
|
18
|
+
# Redis is great to use when you have a bigger/longer wizard form.
|
19
|
+
# Session cookies max size is 4k, so if the size is over that, consider
|
20
|
+
# switching to redis store
|
21
|
+
#
|
22
|
+
#
|
23
|
+
# Session cookies are still used even when using redis as the store location
|
24
|
+
# A key and a uuid is stored on the browser session cookie
|
25
|
+
# That uuid is used as the key in redis to retrieve the form data to the controller
|
26
|
+
def redis_instance
|
27
|
+
store[:redis_instance]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Location tells the gem where to put your form data between form steps
|
31
|
+
# The default is session cookies in the browser
|
32
|
+
def location
|
33
|
+
store[:location]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Logical methods to determine where the gem should store form data
|
37
|
+
def store_in_redis?
|
38
|
+
store[:location] == :redis
|
39
|
+
end
|
40
|
+
|
41
|
+
# Logical methods to determine where the gem should store form data
|
42
|
+
def store_in_cookies?
|
43
|
+
store[:location] =! :redis
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
|
6
|
+
module MultiModelWizard
|
7
|
+
module CookieStore
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
EXPIRATION = 1.hour
|
11
|
+
|
12
|
+
# This method is used to set the session cookie on the browser
|
13
|
+
# EXAMPLE:
|
14
|
+
# set_signed_cookie(key: 'multi_model_wizard_form', value: { hello: 'world' })
|
15
|
+
def set_signed_cookie(attributes)
|
16
|
+
cookies.signed[attributes[:key]&.to_sym] = {
|
17
|
+
value: attributes[:value],
|
18
|
+
expires: attributes[:expires] || EXPIRATION.from_now,
|
19
|
+
same_site: attributes[:same_site] || 'None',
|
20
|
+
secure: attributes[:secure] || true,
|
21
|
+
httponly: attributes[:httponly] || true
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# This method is used to retrieve the session cookie from the browser
|
26
|
+
def get_signed_cookie(key)
|
27
|
+
cookies.signed[key.to_sym]
|
28
|
+
end
|
29
|
+
|
30
|
+
# This method is used to delete the session cookie from the browser
|
31
|
+
def delete_cookie(key)
|
32
|
+
cookies.delete(key.to_sym)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MultiModelWizard
|
4
|
+
module DynamicValidation
|
5
|
+
# Validates attributes using the original model and returns boolean for the given attributes
|
6
|
+
# @params attributes are the names of the form objects methods [Symbol]
|
7
|
+
# @params model_instance is the original model that the form object got the attributes from [ActiveRecord]
|
8
|
+
# @returns returns boolean if the model has no errors [Boolean]
|
9
|
+
def valid_attribute?(*attributes, model_instance:)
|
10
|
+
model_instance.errors.clear
|
11
|
+
|
12
|
+
attributes.flatten!
|
13
|
+
attributes = attributes.first if attributes.first.is_a?(Hash)
|
14
|
+
|
15
|
+
attributes.each do |attribute, validator_types|
|
16
|
+
validators = model_instance.class.validators_on(attribute)
|
17
|
+
|
18
|
+
if validator_types.present?
|
19
|
+
validator_types = Array(validator_types)
|
20
|
+
validators.select! { |validator| validator.kind.in?(validator_types) }
|
21
|
+
end
|
22
|
+
|
23
|
+
validators.each { |validator| validator.validate(model_instance) }
|
24
|
+
end
|
25
|
+
|
26
|
+
model_instance.errors.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validates attributes using the original model and returns a boolean and a message for the given attributes
|
30
|
+
# @params attributes are the names of the form objects methods [Symbol]
|
31
|
+
# @params model_instance is the original model that the form object got the attributes from [ActiveRecord]
|
32
|
+
# @returns returns an object with the valid and messages attributes [OpenStruct]
|
33
|
+
def validate_attribute_with_message( *attributes, model_instance:)
|
34
|
+
model_instance.errors.clear
|
35
|
+
|
36
|
+
attributes.flatten!
|
37
|
+
attributes = attributes.first if attributes.first.is_a?(Hash)
|
38
|
+
|
39
|
+
attributes.each do |attribute, value|
|
40
|
+
validators = model_instance.class.ancestors.map { |x| x.try(:validators_on, attribute) }.compact.flatten
|
41
|
+
|
42
|
+
validators.each { |validator| validator.validate(model_instance) }
|
43
|
+
end
|
44
|
+
|
45
|
+
OpenStruct.new(valid: model_instance.errors.empty?, messages: model_instance.errors.full_messages.uniq )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'multi_model_wizard/cookie_store'
|
4
|
+
require 'multi_model_wizard/config'
|
5
|
+
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/numeric/time'
|
8
|
+
|
9
|
+
module MultiModelWizard
|
10
|
+
module RedisCookieStore
|
11
|
+
include ::MultiModelWizard::CookieStore
|
12
|
+
|
13
|
+
# This method is used to set the form data in redis
|
14
|
+
# @note the key of this method is the congifured form key and the uuid for that form session
|
15
|
+
# EXAMPLE:
|
16
|
+
# set_redis_cache('multi_model_wizard:d5be032f-4863-44e7-87c8-0ec86c85263d', { hello: 'world' })
|
17
|
+
def set_redis_cache(key, data, expire: ::MultiModelWizard::CookieStore::EXPIRATION)
|
18
|
+
wizard_redis_instance.set(key, data, ex: expire)
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method is used to delete the form data from redis
|
22
|
+
def clear_redis_cache(key)
|
23
|
+
wizard_redis_instance.del(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method is used to retrieve the form data from redis
|
27
|
+
def fetch_redis_cache(key)
|
28
|
+
wizard_redis_instance.get(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Reference the redis instance that was passed in from the initializer
|
34
|
+
def wizard_redis_instance
|
35
|
+
::MultiModelWizard.configuration.redis_instance
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Modules
|
4
|
+
require 'multi_model_wizard/redis_cookie_store'
|
5
|
+
require 'multi_model_wizard/dynamic_validation'
|
6
|
+
require 'multi_model_wizard/cookie_store'
|
7
|
+
require 'multi_model_wizard/version'
|
8
|
+
require 'multi_model_wizard/config'
|
9
|
+
require 'multi_model_wizard'
|
10
|
+
require 'form_object/base'
|
11
|
+
|
12
|
+
# Third party gems
|
13
|
+
require 'json'
|
14
|
+
require 'securerandom'
|
15
|
+
require 'active_support'
|
16
|
+
|
17
|
+
module MultiModelWizard
|
18
|
+
module Wizard
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
include ::MultiModelWizard::CookieStore
|
22
|
+
include ::MultiModelWizard::RedisCookieStore
|
23
|
+
|
24
|
+
attr_reader :form_id
|
25
|
+
|
26
|
+
# This gets the form data from the session cookie or redis depending on whats configured
|
27
|
+
def session_params
|
28
|
+
store_in_redis? ? redis_session_params : cookie_session_params
|
29
|
+
end
|
30
|
+
|
31
|
+
# This clears the form data from the session cookie or redis depending on whats configured
|
32
|
+
def clear_session_params
|
33
|
+
store_in_redis? ? clear_redis_session_params : clear_cookie_session_params
|
34
|
+
end
|
35
|
+
|
36
|
+
# This sets the form data in the session cookie or redis depending on whats configured
|
37
|
+
def set_session_params(value)
|
38
|
+
store_in_redis? ? set_redis_session_params(value) : set_cookie_session_params(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Wizard form uuid will attempt to get the uuid from the browser session cookie
|
42
|
+
# If one is not there it will set a new session cookie with the uuid as teh value
|
43
|
+
def wizard_form_uuid
|
44
|
+
return get_signed_cookie(wizard_form_key) if get_signed_cookie(wizard_form_key).present?
|
45
|
+
|
46
|
+
@uuid ||= SecureRandom.uuid
|
47
|
+
set_signed_cookie(key: wizard_form_key, value: @uuid)
|
48
|
+
@uuid
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reference the form key that was passed in from the initializer
|
52
|
+
def multi_model_wizard_form_key
|
53
|
+
::MultiModelWizard.configuration.form_key
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def wizard_form_key
|
59
|
+
if form_id
|
60
|
+
"#{multi_model_wizard_form_key}#{form_id}".to_sym
|
61
|
+
else
|
62
|
+
multi_model_wizard_form_key.to_sym
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Logical methods to determine where the gem should store form data
|
67
|
+
def store_in_redis?
|
68
|
+
::MultiModelWizard.configuration.store_in_redis?
|
69
|
+
end
|
70
|
+
|
71
|
+
# This method is used to retrieve the form data from redis
|
72
|
+
def redis_session_params
|
73
|
+
JSON.parse(fetch_redis_cache("#{wizard_form_key.to_s}:#{wizard_form_uuid}"))
|
74
|
+
rescue TypeError
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
def clear_redis_session_params
|
79
|
+
clear_redis_cache("#{wizard_form_key.to_s}:#{wizard_form_uuid}")
|
80
|
+
delete_cookie(wizard_form_key.to_sym)
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_redis_session_params(value)
|
84
|
+
set_redis_cache(
|
85
|
+
"#{wizard_form_key.to_s}:#{wizard_form_uuid}",
|
86
|
+
value,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def cookie_session_params
|
91
|
+
get_signed_cookie(wizard_form_key.to_s)
|
92
|
+
end
|
93
|
+
|
94
|
+
def clear_cookie_session_params
|
95
|
+
delete_cookie(wizard_form_key)
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_cookie_session_params(attributes)
|
99
|
+
set_signed_cookie(attributes.merge(key: wizard_form_key.to_s))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'multi_model_wizard/dynamic_validation'
|
4
|
+
require_relative 'multi_model_wizard/redis_cookie_store'
|
5
|
+
require_relative 'multi_model_wizard/cookie_store'
|
6
|
+
require_relative 'multi_model_wizard/version'
|
7
|
+
require_relative 'multi_model_wizard/wizard'
|
8
|
+
require_relative 'multi_model_wizard/config'
|
9
|
+
require_relative 'form_object/base'
|
10
|
+
|
11
|
+
|
12
|
+
module MultiModelWizard
|
13
|
+
class << self
|
14
|
+
def configuration
|
15
|
+
@configuration ||= ::MultiModelWizard::Config.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def configure
|
19
|
+
yield(configuration)
|
20
|
+
end
|
21
|
+
|
22
|
+
def version
|
23
|
+
::MultiModelWizard::VERSION
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/multi_model_wizard/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'multi_model_wizard'
|
7
|
+
spec.version = MultiModelWizard::VERSION
|
8
|
+
spec.authors = ["micahbowie-pu"]
|
9
|
+
spec.authors = ["proctoru"]
|
10
|
+
spec.email = ["ruby-gems@meazurelearning.com "]
|
11
|
+
|
12
|
+
spec.summary = 'Creates a smart object for your wizards or forms. Create one form and form object that can update multiple models with ease.'
|
13
|
+
spec.description = 'MultiModelWizard is a way to create and update multiple ActiveRecord models using one form object.'
|
14
|
+
spec.homepage = 'https://github.com/ProctorU/multi_model_wizard'
|
15
|
+
spec.required_ruby_version = ">= 2.5.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = 'https://rubygems.org/'
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
21
|
+
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency 'activemodel', '>= 5.0'
|
32
|
+
spec.add_dependency 'activesupport', '>= 5.0'
|
33
|
+
|
34
|
+
spec.add_development_dependency 'activerecord', '>= 5.0'
|
35
|
+
spec.add_development_dependency 'sqlite3'
|
36
|
+
spec.add_development_dependency 'byebug'
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multi_model_wizard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- proctoru
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: MultiModelWizard is a way to create and update multiple ActiveRecord
|
84
|
+
models using one form object.
|
85
|
+
email:
|
86
|
+
- ruby-gems@meazurelearning.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".rspec"
|
92
|
+
- CHANGELOG.md
|
93
|
+
- Gemfile
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/form_object/base.rb
|
97
|
+
- lib/multi_model_wizard.rb
|
98
|
+
- lib/multi_model_wizard/config.rb
|
99
|
+
- lib/multi_model_wizard/cookie_store.rb
|
100
|
+
- lib/multi_model_wizard/dynamic_validation.rb
|
101
|
+
- lib/multi_model_wizard/redis_cookie_store.rb
|
102
|
+
- lib/multi_model_wizard/version.rb
|
103
|
+
- lib/multi_model_wizard/wizard.rb
|
104
|
+
- multi_model_wizard.gemspec
|
105
|
+
homepage: https://github.com/ProctorU/multi_model_wizard
|
106
|
+
licenses: []
|
107
|
+
metadata:
|
108
|
+
allowed_push_host: https://rubygems.org/
|
109
|
+
homepage_uri: https://github.com/ProctorU/multi_model_wizard
|
110
|
+
source_code_uri: https://github.com/ProctorU/multi_model_wizard
|
111
|
+
changelog_uri: https://github.com/ProctorU/multi_model_wizard
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 2.5.0
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubygems_version: 3.0.9
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Creates a smart object for your wizards or forms. Create one form and form
|
131
|
+
object that can update multiple models with ease.
|
132
|
+
test_files: []
|