dmitrytsepelev / store_model Goto Github PK
View Code? Open in Web Editor NEWWork with JSON-backed attributes as ActiveRecord-ish models
License: MIT License
Work with JSON-backed attributes as ActiveRecord-ish models
License: MIT License
Thanks for the great gem! First time user.
Implementation works well except when I run my specs I get the error noted above after switching my attributes from :jsonb to store_model
. Nothing complex about the app/implementation.
Any thoughts on how to best handle?
class WorkerOption < ApplicationRecord
# attribute :options, :jsonb, default: {}
# attribute :data, :jsonb, default: {}
attribute :data, WoCases.to_type
attribute :options, Options.to_type
end
schema.rb
create_table "worker_options", id: :serial, force: :cascade do |t|
t.jsonb "options", default: "{}", null: false
t.jsonb "data", default: "{}", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
running rspec
An error occurred in a `before(:suite)` hook.
Failure/Error: FactoryBot.lint
FactoryBot::InvalidFactoryError:
The following factories are invalid:
* worker_option - When assigning attributes, you must pass a hash as an argument. (ArgumentError)
# /Users/jrpowell/.gem/ruby/2.6.5/gems/factory_bot-5.1.1/lib/factory_bot/linter.rb:13:in `lint!'
# /Users/jrpowell/.gem/ruby/2.6.5/gems/factory_bot-5.1.1/lib/factory_bot.rb:74:in `lint'
# ./spec/support/init/factory_bot_rails.rb:21:in `block (2 levels) in <top (required)>'
/spec/support/init/factory_bot_rails.rb
RSpec.configure do |config|
# Allows the use of "create :user" as a shorthand for "FactoryBot.create :user".
config.include FactoryBot::Syntax::Methods
config.before(:suite) do
# This forces FactoryBot to recognize changes in factories. It's useful for process forking with spring & spork.
FactoryBot.reload
begin
# Prep DatabaseCleaner because the call to FactoryBot.lint below will leave models in the database.
DatabaseCleaner.start
# Ensure factories generate valid objects
FactoryBot.lint
ensure
# Clean the database because FactoryBot.lint left models in database.
DatabaseCleaner.clean
end
end
end
factories/worker_option.rb
FactoryBot.define do
factory :worker_option, class: 'WorkerOption' do
data { { "uri" => 'http://localhost:3000/api/v1/work_flows/1/case_machines/1/cases' } }
trait :valid_options do
options { { "id" => 666, "number" => "NUMBER", "receipt_number" => "RECEIPT_NUMBER" } }
end
end
end
Hi @DmitryTsepelev,
Thanks for the gem! It seems allow_nil: true
not working, when I try to save model with json attributes set to nil it raises
raise StoreModel::Types::CastError,
"failed casting #{value.inspect}, only String or Array instances are allowed"
or
raise StoreModel::Types::CastError,
"failed casting #{value.inspect}, only String, " \
"Hash or #{@model_klass.name} instances are allowed"
If I add
when nil then value
in both array_type.rb and json_type.rb everything works fine.
Hi, I was wondering if it is possible to use an ActiveRecord::Enum
for an attribute type, to store the value as an integer, but gain the associated methods? Thanks!
I have similar setup as below documentation to store json array.
https://github.com/DmitryTsepelev/store_model/blob/master/docs/array_of_stored_models.md
I'm getting below error when I tried to save the model that uses array of store model.
class Product < Application Record
attribute :variants, Variant.to_array_type
end
class Variant
include StoreModel::Model
attribute :name, :string
end
product = Product.new
product.variants = [{ name: 'foo' }]
product.variants # [#<Variant name: 'foo'>]
product.save
> NoMethodError: undefined method `to_hash' for nil:NilClass
from /Users/taufek/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activemodel-6.0.3.1/lib/active_model/attributes.rb:97:in `attributes'
Hi there! First off thanks for the gem, secondly this is a feature request which I'm happy to work at.
Our use case has a client, and this client can turn on certain customizations. If customization is turned on then all items in that client can set there own individual values for that customization.
For example, let's say a client has the customization foo
and bar
turned on an item inside of that client will have a jsonb data structure of
{
foo: {
},
bar: {
}
}
And another client only has the customization type foobar
turned on the jsonb data for an item will look like
{
foobar: {
}
}
We have about maybe 40 of these customizations (and this could grow dynamically), which means that creating StoreModel class for each of the customizations is not an ideal option. I'm handling this currently by using a mix of method_missing
and the unknown_attributes
method in your gem
class Configuration
include StoreModel::Model
def method_missing(m, *args, &block)
unknown_attributes[m.to_s]
end
end
This works great for reading data, but the problem comes when I'm trying to update or save data. From what I can tell, b/c there are no attributes assigned to the Configuration
class, when it tries to cast the values it just raises the raise_cast_error(value)
and does not accept the params being passed. I want to add some sort of override which will just push whatever data I give to it and ignore the validation in the json_type file.
Let me know if that all makes sense, and let me know if this is something you would be willing to add to the gem. I'm happy to make a PR for it.
It would be nice to have something like #parent
method, to get store model's parent object.
I just thought it might be useful. And I would definitely appreciate it in my project :)
It seems like it might be an additional step during object initialization. (and in more places)
I also think it is potentially related to #30, as it kind of "tightens" the connection between parent and store model.
Hi, this is neat! I've used a similar approach with Rails Attributes API, in a somewhat different direction, in attr_json.
I see you link in the README to store_attribute and jsonb_accessor, would you consider linking to attr_json too?
Currently attr_json meets a somewhat different use case than store_accessor: I wanted to store both arrays of primitives and models in a single json column, to sort of give you some embedded "NoSQL"-like functionality in an activerecord. I also wanted to support nested models (a model can include other models as an attribute), and convenient support for using rails form builder (or simple_form) with all your embedded/nested models.
attr_json doesn't at the moment support the particular use case of store_model, with a single model that maps to an entire json column (instead attr_json wants it to map to a key in a hash in a json column). It might in the future though, it's been asked for.
I wonder if there's a way for us to collaborate/join forces, but also think it's not too much of a problem that we both are doing our thing, that's how innovation and experimentation works! I think it's neat that the Rails Attribute API allows us to do these things!
ActiveRecord decorates :datetime
and :time
attributes with a TimeZoneConverter
type.
This ensures the time is converted automatically into the current Time.zone
.
I'm currently working around this issue by explicitly decorating the type.
class Todo
include StoreModel::Model
attribute :due_at, ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter.new(ActiveModel::Type::Time.new)
end
Any thoughts on if it is desirable to have the same behaviour as ActiveRecord for dates and times?
This could be also made configurable through something like StoreModel.config.time_zone_aware_attributes = true
and StoreModel.config.time_zone_aware_types = [:datetime, :time]
.
I am getting below error while saving the object into the database having store model attribute of to_array_type
Java::JavaLang::ClassCastException (class org.jruby.RubyNil cannot be cast to class org.jruby.RubyHash (org.jruby.RubyNil and org.jruby.RubyHash are in unnamed module of loader 'bootstrap'))
Hello,
I am trying to create a form where my models can be created in a nested fashion as well as multiple at once.
Here is my code:
class Order < ApplicationRecord
attribute :treatment, Treatment.to_type, default: Treatment.default
end
class Treatment
include StoreModel::Model
MATERIALS = %i[zirkon ceramik metal]
COLORS = %i[A2 A1]
enum :material, MATERIALS, default: :zirkon
enum :color, COLORS, default: :A2
attribute :teeth, Teeth.to_array_type, default: []
accepts_nested_attributes_for :teeth
# https://www.dentforme.de/service/hkp_verstehen/kuerzel_behandlungsplanung/
def self.default
new.tap do |treatment|
treatment.teeth << Teeth.new(id: '18')
treatment.teeth << Teeth.new(id: '17')
treatment.teeth << Teeth.new(id: '16')
end
end
end
class Teeth
include StoreModel::Model
APPROACHES = %i[NONE PKM]
attribute :id, :string
enum :approach, APPROACHES, default: :NONE
end
= form_for @order do |f|
- if @order.errors.any?
#error_explanation
%h2= "#{pluralize(@order.errors.count, "error")} prohibited this order from being saved:"
%ul
- @order.errors.full_messages.each do |message|
%li= message
.field
= f.label :treatment
= f.fields_for :treatment do |tf|
.field
= tf.label :material
= tf.select :material, Treatment::MATERIALS.map { |w| [w.to_s.humanize, w] }
.field
= tf.label :color
= tf.select :color, Treatment::COLORS.map { |w| [w.to_s.humanize, w] }
.field
= tf.label :teeth
= tf.fields_for :teeth do |ttf|
- @order.treatment.teeth.each do |teeth|
= ttf.fields_for teeth.id, teeth do |tttf|
= tttf.label :approach
= tttf.select :approach, Teeth::APPROACHES.map { |w| [w.to_s, w] }
With this setup, I get the following error:
StoreModel::Types::CastError Exception: failed casting {"18"=>{"approach"=>"NONE"}, "17"=>{"approach"=>"PKM"}, "16"=>{"approach"=>"NONE"}}, only String or Array instances are allowed
I feel that my form does feel very nice, as I have to iterate over the teeth instances. But otherwise it does not work either, as the fields for helper does not detect the array.
Which will preserve existing nested attributes :)
For me, it's quite a boilerplate code now, and it's useful, so I think it might be handy to be here out of the box.
Here's an example of something I already have:
class Site
class Settings
include StoreModel::Model
NESTED_MODELS_TO_PRESERVE = %i[colours style].freeze
attribute :twitter_handle, :string
# ...
attribute :colours, ColourScheme.to_type, default: -> { ColourScheme.new }
attribute :style, Styling.to_type, default: -> { Styling.new }
attribute :footer_pages, FooterPage.to_array_type, default: -> { [] }
def assign_preserving(attrs)
attrs = attrs.dup
NESTED_MODELS_TO_PRESERVE.each do |model_key|
next unless (nested_model = send(model_key))
next unless (nested_attrs = attrs.delete(model_key))
nested_model.assign_preserving(nested_attrs)
end
# NOTE: footer_pages are not being "preserved"
# and are overwritten if given, because it is an array
assign_attributes(attrs)
end
end
end
And all the nested models have the same assign_preserving
method, however their NESTED_MODELS_TO_PRESERVE
might be blank.
Yeah, and I guess we might have something more sophisticated than a constant of all nested models.
Hi,
Great library, have migrated already out settings stores to this library and works great.
One thing that it would be great is for enum types to support prefix settings.
Currently to avoid conflicts for many enum settings it would be good to have this similar feature from enum rails core
eg.
enum :authentication_method, %i[email phone], default: :email
enum :notification_method, %i[email phone], default: :email
currently we get arround this by doing this,
enum :authentication_method, %i[authentication_method_email authentication_method_phone], default: :authentication_method_email
enum :notification_method, %i[notification_method_email notification_method_phone], default: :notification_method_email
however, would be cleaner to have
enum :authentication_method, %i[email phone], default: :email, _prefix: true
enum :notification_method, %i[email phone], default: :email, _prefix: true
def ==(other)
return super unless other.is_a?(self.class)
attributes.all? { |name, value| value == other.send(name) }
end
The value
in this case is going to be an integer, while the result of other.send(name)
will be a string enum key.
The array example here
https://github.com/DmitryTsepelev/store_model/blob/master/docs/validations.md
should use merge_array_errors
vs merge_array
:
class Product < ApplicationRecord
attribute :configurations, Configuration.to_array_type
validates :configurations, store_model: { merge_array_errors: true } # <--- TWEAK
end
product = Product.new
product.configurations << Configuration.new
puts product.valid? # => false
puts product.errors.messages # => { color: ["[0] Color can't be blank"] }
Should it be possible to access the attribute details programatically as per a normal ActiveRecord class?
ie should the following work (or is there another way)
class Configuration
include StoreModel::Model
attribute :model, :string
attribute :foo, :string
end
Configuration.attribute_names
=> ["model", "foo"]
I can do it ok on an instance with
Configuration.new.attributes.keys
=> ["model","foo"]
I use this so that any new attributes are available via the API.
More details to be added, reference is here
Got this warning during rake task implementation.
DEPRECATION WARNING: Initialization autoloaded the constants ArrayOfStringsType and ArrayOfIntegersType.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload ArrayOfStringsType, for example,
the expected changes won't be reflected in that stale Class object.
These autoloaded constants have been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
class ArrayOfIntegersType < ActiveRecord::Type::Value
def type
:array_of_integers
end
def cast(values)
return if values.blank?
values.map(&:to_i)
end
end
class ArrayOfStringsType < ActiveRecord::Type::Value
def type
:array_of_strings
end
end
I have a class namedShop::OptinSettings
with nested store_models
class Shop::OptinSettings
include StoreModel::Model
attribute :terms_of_service, :string, default: I18n.t("shop.optin_settings.terms_of_service")
attribute :modal, Shop::OptinSettings::Details.to_type, default: Shop::OptinSettings::Details.new
attribute :page, Shop::OptinSettings::Details.to_type, default: Shop::OptinSettings::Details.new
end
In my class Shop::OptinSettings::Details
I aim to dynamically set a default value dependent on the attribute having the name modal
or page
.
class Shop::OptinSettings::Details
include StoreModel::Model
def self.parent_attribute
...
end
attribute :body, :string, default: I18n.t("shop.optin_settings.#{parent_attribute}.title")
attribute :title, :string, default: I18n.t("shop.optin_settings.#{parent_attribute}.title")
end
Is there a method that would allow me to return this value?
Is it possible to have store_model to store unknown attributes? The .unknown_attributes method is extremely useful however upon saving the record the unknown attributes are lost. It would be useful to save these attributes so they can later be added to the model and their value would then be accessible.
I guess it's a feature request, so let me know if I can help in any way:
Is it possible to define ActiveRecord-like scopes when using #to_array_type
? For example I have this models:
class Product < ApplicationRecord
attribute :properties, Property.to_array_type
end
class Property
include StoreModel::Model
attribute :key, :string
attribute :value, :string
attribute :locale, :string
scope :hidden, ->{ select { |prop| prop.key.start_with?("_") } }
scope :english, locale: "en"
end
Now when I want every property where the key is beginning with an underscore (it's a convention we use) it would be very handy to define them like this, and access them like Product.first.properties.hidden.english
.
And it would be more awesome if you could use array operations like intersection, uniq, addition, substraction... and still be able to use this scopes, for example:
props1 = Product.first.properties
props2 = Product.last.properties
(props1 + props2).uniq.hidden.english
But I'm not sure if this is a good idea.
@DmitryTsepelev Can you share how you're using this gem on the frontend layer (meaning controller params and form views)? I'm facing some issues with assign_attributes
overriding the entire hash. If possible, provide more information about these issues on the README file.
I was wondering if any of you have used rails_admin to edit fields accessible via store_model. If so I am looking for best practices!
Thank you.
If I have a nested model setup like so:
class Story
attribute :primary_visuals, PrimaryVisuals.to_type
end
class PrimaryVisuals
include StoreModel::Model
attribute :primary_image, Image.to_type
attribute :primary_video, Video.to_type
end
class Image
include StoreModel::Model
attribute :slug, :string
attribute :guid, :string
attribute :long_caption, :string
attribute :short_caption, :string
end
I'd expect to be able to do something like:
story = Story.new
story.primary_visuals = PrimaryVisuals.new
story.primary_visuals.primary_image = Image.new(slug: "test")
story.save!
story.primary_visuals.primary_image # => #<Image slug: test...
But instead I get nil on the primary_image
Is this expected behavior or have I tripped over a bug?
Hi, I wondered if this behavior is intentional:
lambda_strategy = lambda do |attribute, base_errors, _store_model_errors|
_store_model_errors.each do |field, message|
base_errors.add("#{attribute}.#{field}", message)
end
end
validates :thing, store_model: lambda_strategy, on: :create
I like it. If it's intentional, I would be nice to get it documented
Hi,
I have encountered an issue with errors being supressed from the main model. I am using the following versions.
Rails: 6.0.3.4
store_model 0.8.1
ruby version - 2.7.2
Here is my code -
class NotificationConfiguration
include StoreModel::Model
EMAIL_REGEXP = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.freeze
validates :secondary_email, format: { with: EMAIL_REGEXP }, allow_blank: true
end
class User < ApplicationRecord
attribute :configuration, BackupConfiguration.to_type
validates_presence_of :name
validates :configuration, store_model: { merge_errors: true }
before_validation :build_notification_configuration, on: [:create]
end
user = User.create(name: 'test')
user.configuration.secondary_email = 'example'
user.name = ''
user.valid?
user.errors # => shows only 1 error instead of 2.
If we modify the order of validations to maintain validates :configuration ...
before validates_presence_of :name
, it displays 2 errors instead of 1.
First of all, StoreModel is awesome and we've been using it more and more in my teams project. We've been using it mostly for config type of data but I'm experimenting with using it for polymorphic records stored on a ActiveRecord model. One thing I think would be helpful (but maybe out of scope of this gem) is implementing before_validation
and and after_validation
hooks.
This could be useful in a number of ways. We could use before_validation to auto set values of fields. E.g. if we want to set randomly generated token as a unique identifier.
class FooStoreModel
include StoreModel::Model
attribute :token, :string
before_validation :set_token, on: :create
private
def set_token
token = SecureRandom.uuid
end
end
Another way this could be useful is for replicating STI type of inheritance. The polymorphic typing is awesome and I think it should conform to the Rails way of polymorphic STI (using a type variable).
class AbstractStoreModel
include StoreModel::Model
attribute :type, :string
before_validation :set_type, on: :create
private
def set_type
type= self.class.name
end
end
class FooStoreModel < AbstractStoreModel
end
class BarStoreModel < AbstractStoreModel
attribute :value, :string
end
class FooRecord < ActiveRecord::Base
StoreModelsType = StoreModel.one_of do |json|
case json['type']
when 'FooStoreModel'
FooStoreModel
when 'BarStoreModel'
BarStoreModel
else
raise InvalidTypeError
end
end
attribute :data, StoreModelsType.to_array_type
end
This would allow something like
record = FooRecord.create!
record.data << BarStoreModel.new(value: 'baz')
record.save
which would auto cast and infer types
Is it possible to method chain dynamically? Something like the following?
def self.send_chain(methods)
methods.inject(self, :send)
end
Where I could:
> Article.send_chain(["with_author", "pending_review"])
It would be nice to be able to use this gem with ActiveRecord without having a Rails application.
Hello,
I was wondering, why during serialization my custom types don't get used to determine the json format:
class DateOrTimeType < ActiveRecord::Type::Value
def type
:json
end
def cast_value(value)
case value
when String
decoded = ActiveSupport::JSON.decode(value) rescue nil
build_from(decoded)
when Hash
build_from(value)
when Date, Time, DateTime
value
end
end
def serialize(value)
case value
when Date
ActiveSupport::JSON.encode(date: value)
when Time, DateTime
ActiveSupport::JSON.encode(datetime: value)
else
super
end
end
def changed_in_place?(raw_old_value, new_value)
cast_value(raw_old_value) != new_value
end
private
def build_from(hash)
if hash['date'].present?
Date.parse(hash['date'])
else
Time.parse(hash['datetime'])
end
end
end
class Appointments::Schedule
include StoreModel::Model
attribute :from, DateOrTimeType.new
attribute :to, :date_or_time
but during serialization, the default json serialisation of the given type is used.
Hi there,
It looks like when an attribute is removed from a StoreModel::Model
and a value exists for it in the JSON, Rails is raising an ActiveModel::UnkownAttributeError
when deserialising from the database. This is happening to me on a nested model.
Possibly related to issue #22
Thanks!
Hi!
So I was playing around with this gem, and tried the following:
I have defined some JSON-backed model, let's say it was:
class Configuration
include StoreModel::Model
attribute :model, :string
end
Then I have created some record in my DB with filled model
attribute.
Then I have renamed that model
attribute to something_else
.
And form this point if I load my stored record from DB, and trying to access JSON-backed model - I'm getting an error: ActiveModel::UnknownAttributeError
.
So I think it's not very flexible. This makes it harder to change that JSON-schema over time.
I think that we could benefit from using same approach as mongoid
uses here:
if JSON has some extra attributes - no errors raised, there's no such attribute, so just "ignore" it, but, that data is still accessible from attributes
hash.
What do you think?
How can we handle data migration in underlying jsonb field?
Here's an example. I've defined the following structure for AssetIssueList v1
class AssetIssue
include StoreModel::Model
attribute :key, :string, default: ''
attribute :name, :string, default: ''
attribute :exists, :boolean, default: false
validates :key, name, :exists, presence: true
end
class AssetIssueList
include StoreModel::Model
attribute :version, :integer, default: 1
attribute :list, AssetIssue.to_array_type, default: [ ]
validates :version, :list, presence: true
end
class Asset < ApplicationRecord
attribute :issues, AssetIssueList.to_type
after_find :validateIssues
def validateIssues
if issues.nil? || issues.version != ISSUES_VERSION
updateDefaultIssues
end
end
end
After some time, I've decided to remove attribute :name
from AssetIssue. Application is unable to start due to error
ActiveModel::UnknownAttributeError (unknown attribute 'name' for AssetIssue.)
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_assignment.rb:53:in `_assign_attribute'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_assignment.rb:44:in `block in _assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_assignment.rb:43:in `each'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_assignment.rb:43:in `_assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_assignment.rb:35:in `assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/model.rb:81:in `initialize'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attributes.rb:80:in `initialize'
/usr/local/bundle/gems/store_model-0.5.2/lib/store_model/types/array_type.rb:78:in `new'
/usr/local/bundle/gems/store_model-0.5.2/lib/store_model/types/array_type.rb:78:in `block in ensure_model_class'
/usr/local/bundle/gems/store_model-0.5.2/lib/store_model/types/array_type.rb:77:in `map'
/usr/local/bundle/gems/store_model-0.5.2/lib/store_model/types/array_type.rb:77:in `ensure_model_class'
/usr/local/bundle/gems/store_model-0.5.2/lib/store_model/types/array_type.rb:34:in `cast_value'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/type/value.rb:38:in `cast'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute.rb:174:in `type_cast'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute.rb:42:in `value'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_set.rb:41:in `fetch_value'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attributes.rb:130:in `attribute'
/usr/local/bundle/gems/activemodel-6.0.0/lib/active_model/attribute_methods.rb:383:in `list'
/app/models/navigation_asset.rb:14:in `validateIssues'
How to deal with that? Couldn't be not existent static keys (name in my example) just dynamic in AssetIssueList
so we can migrate underlying data storage with ease?
I think I've found one more issue.
After updating to 0.3.1 and trying to save model with attribute .to_array_type
NoMethodError - undefined method `invalid?' for #<Array:0x00007f97219194d8>:
from
After adding:
elsif value.respond_to?(:invalid) && value.invalid?
(value
is Array
)
Seems to be fine.
Let me know if you need more info.
Thanks!
Hi @DmitryTsepelev,
are you planning to add the accept_nested_attributes_for
function to the store_model?
I have a type X and X has one Y, but Y can be Y1 or Y2 or Y3, and each of them has different attributes, but they all subclass Y_base
The documentation does not specify a serialized column type, however the opening line suggests JSONB is supported
"Imagine that you have a model Product with a jsonb column called configuration. This is how you likely gonna work with this column:"
However, creating a column with jsonb does no persist the serialized values, a json column does
This is with Rails 6, postgres. Are you aware of issues around jsonb?
I'm getting below error when using store_model
with audited
gem in a model.
class Product < Application Record
audited
attribute :info, Info.to_type
end
class Info
include StoreModel::Model
attribute :name, :string
end
product = Product.new
product.info = { name: 'foo' }
product.save
> NoMethodError: undefined method `to_hash' for nil:NilClass
from /Users/taufek/.asdf/installs/ruby/2.7.1/lib/ruby/gems/2.7.0/gems/activemodel-6.0.3.1/lib/active_model/attributes.rb:97:in `attributes'
Hi! Why is a predicate method not defined for boolean attributes?
Are there any obstacles here?
class Settings
include StoreModel::Model
attribute :something_enabled, :boolean, default: false
end
record.settings.something_enabled? #=> undefined method ((
When the attributes
key is used with a hash, and the hash keys are unknown, an infinite loop is triggered in the unknown attributes handler. value_symbolized.except(:attributes)
doesn't remove :attributes
, and instead tries to remove the incorrect key inside the attributes hash.
class Configuration
include StoreModel::Model
attribute :color, :string
end
class ConfigurationWithNesting
include StoreModel::Model
attribute :configuration, Configuration.to_type
end
# Non-nested creation with incorrect attribute, with attributes: key
Configuration.new(attributes: { test: 123 })
# Errors correctly
# ActiveModel::UnknownAttributeError: unknown attribute 'test' for Configuration.
# Creation with correct attribute, with attributes: key
ConfigurationWithNesting.new(configuration: { attributes: { color: 123 } })
# Works correctly
# #<ConfigurationWithNesting configuration: #<Configuration:0x00005588e7084c40>>
# Creation with incorrect attribute, with attributes: key
ConfigurationWithNesting.new(configuration: { attributes: { test: 123 } })
# Infinite loop
After interrupting:
(pry) output error: Interrupt
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:52:in `_assign_attribute'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:43:in `block in _assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:42:in `each'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:42:in `_assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:35:in `assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:50:in `public_send'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:50:in `_assign_attribute'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:43:in `block in _assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:42:in `each'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:42:in `_assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attribute_assignment.rb:35:in `assign_attributes'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/model.rb:81:in `initialize'
/usr/local/bundle/gems/activemodel-6.0.3.4/lib/active_model/attributes.rb:79:in `initialize'
/usr/local/bundle/gems/store_model-0.8.0/lib/store_model/types/one.rb:65:in `new'
/usr/local/bundle/gems/store_model-0.8.0/lib/store_model/types/one.rb:65:in `model_instance'
/usr/local/bundle/gems/store_model-0.8.0/lib/store_model/types/one.rb:33:in `cast_value'
/usr/local/bundle/gems/store_model-0.8.0/lib/store_model/types/one_base.rb:60:in `handle_unknown_attribute'
/usr/local/bundle/gems/store_model-0.8.0/lib/store_model/types/one.rb:38:in `rescue in cast_value'
... loops endlessly after this
In both cases, value_symbolized.except(attribute)
tries to remove :test
, but in the nested case, :attributes
should be removed instead. This seems to be because ActiveModel::UnknownAttributeError
is not raised with :attributes
.
Hi! We often have the situation where a model has an array of strings that needs to be validated to only include certain values, and we need some predicate methods to check if a model has a certain value inside this array:
class User < ApplicationRecord
after_initialize { self.roles = Array.wrap(roles) }
ROLES = %w[admin user reporter].freeze
validate do
errors.add(:roles, :invalid_roles) if (roles - ROLES).any?
end
ROLES.each do |role|
define_method("#{role}?") do
roles.include?(role)
end
end
end
And IMHO, that looks suspiciously like a StoreModel use case: An array of enums. Only difference would be that a wrong role would raise an exception instead of a validation error (but in my case this would be ok).
As far as I can tell, this isn't possible right now with StoreModel
, am I right? Would it be a feature you would consider to include? I'd be happy to try for a PR if you could give me some hints how to implement a feature like this.
When I create a new attribute that has a default and validates its presence, when I save my model I get Validation failed: <attribute> cannot be blank
. It should set the attribute to its default value
attribute :syncing, :boolean, default: false
validates :syncing, presence: true
Now that store_model
respond to a method named type_for_attribute
, it is incompatible with simple_form
gem because of these two lines as you can see here.
I had to copy and paste has_attribute?
implementation from
ActiveRecord::AttributeMethods
module to make it work again with simple_form
.
def has_attribute?(attr_name)
@attributes.key?(attr_name.to_s)
end
@DmitryTsepelev Can you include the above method on the StoreModel base class, please?
Hi!
How I can get access to class constants?
For example I have:
promo.rb
class Promo < ApplicationRecord
USER_PERMISSIONS = %w[update_profile upload_receipt].freeze
attribute :settings, PromoSettings.to_type, default: {}
end
and promo_settings.rb
class PromoSettings
include StoreModel::Model
attribute :user_permissions, :array_of_strings, default: []
validates :user_permissions,
inclusion: {
in: ::Promo::USER_PERMISSIONS
}
end
returns uninitialized constant Promo::USER_PERMISSIONS
Is it ok? Can you help with this? tnx
Hi I am having issues when trying to update settings using fields_for nested attributes in forms. Below the the following way i am trying to implement it based on the comments in read in the PR
in view
<%= form_for @tenant, url: settings_authentications_url(), method: :put do |f| %>
<%= render 'shared/error_messages', object: @tenant.settings %>
<%= f.fields_for :settings do |o| %>
...
<% end %>
<% end %>
class Tenant
attribute :settings, TenantConfiguration.to_type
validates :settings, store_model: true
accepts_nested_attributes_for :settings
end
class TenantConfiguration
include StoreModel::Model
accepts_nested_attributes_for :settings
end
Hi @DmitryTsepelev ,
the gem work with Rails Attributes API
, but with type array
or range
it does not work (). (e.g. attribute :my_int_array, :integer, array: true
you get error ArgumentError: unknown keyword: array
).
I need an attribute with array of strings e.g.:
class Configuration
include StoreModel::Model
attribute :colors, :string, array: true, default: []
end
get error:
ArgumentError: unknown keyword: array
It will be great to have this function. Do you planning to add custom type for it?
Hello, I have a a json column called data as follow. It has name, email, description.
How can I custom validate the email for example?
I'm successful with validating using presence, format and other validations
But how can I use a custom validator? So I want to validate the email using a 3rd party gem.
Something like this:
address = ValidEmail2::Address.new(value)
errors.add(key.to_s, "isn't a valid email") unless address.valid?
How can I access the value of this attribute and the errors?
Also, is it possible to pass a parameter to the configuration?
Thank you
Is it possible to pass a parameter to the configuration?
So I have a json column I'm validating, but the keys I'm validating depend on an enum
So instead of using multiple configurations for each key in the enum, I want to use just one configuration and pass the enum key to it, since all enums have the same validations logic, just different keys
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.