ActWithBag

Gem Version GEM Downloads

Bag helps when fields in a Rails database table are not yet settled down or when many fields without business logic are required.

Installation

As usual:

# Gemfile
gem "act_with_bag"

and run "bundle install".

Usage

Install (migrate) one bag as a text field in a table to collect many fields. Additional fields or removal of them are easy; no migrations are required for them.

Keep in mind that the collection is kept in a YAML bag, i.e. SQL commands can't access the bag fields.

Boolean and Date fields require explicit typing.

Fields without typing accept any values which YAML can handle (e.g. @order.colors = ['red', 'yellow']).

Types :integer, :float and :string forces a conversion (.to_i, .to_f, .to_s) before storing the value, a convenience similar to ActiveRecord handling due to migration definitions.

Technical background: getters and putters are injected into models.

If baggies of type :date are being used then params must be corrected before an update_attributes. Warning: :date fields are not well integrated; avoid them.

Obsolete fields are deleted before_save by:

delete_from_bag :field

Warning

  1. Please add a:

    serialize :bag, Hash

to each subclass accessing a bag field from a superclass. Using an "add_to_bag" in the subclass obsoletes the "serialize".

  1. delete_from_bag just delete the field from the record being saved. Other records are untouched, i.e. the value of the field will be kept in the database.

  2. Keep an eye on YAML, the library to serialize the "bag", which for some versions is not tamper-proof and may required additional configuration in Rails.

See also:

https://stackoverflow.com/questions/72970170/upgrading-to-rails-6-1-6-1-causes-psychdisallowedclass-tried-to-load-unspecif

The configuration "Rails.application.config.active_record.use_yaml_unsafe_load = true" is not recommended as it is a patch prone to attacks.

The configuration "Rails.application.config.active_record.yaml_column_permitted_classes = [Symbol]" may be incomplete requiring additional classes like "Time" and "Date". (credits to Martin Sch?ttler).

Example

In model:

class Order < ActiveRecord::Base
 add_to_bag :name,
  :color,
  :description,
  {idx: :integer},
  {price: :float},
  {active: :boolean},
  {paused_at: :date},
  {msg: :string}

 def to_s
  "Order #{name} #{color} #{price}"
 end
 ...

In controller:

class OrdersController < ApplicationController

 def create
  params = Order.merge({}, self.params)   # only if type :date is being used
  @order = Order.new(params[:order])
  @order.price = 1.23
  logger.info "Order #{@order.to_s} repriced to #{@order.price}"
  ...
 def update
  @order = Order.find(params[:id])
  params = Order.merge(@order.bag, self.params) # only if type :date is being used
  @order.update_attributes(params[:order])

Test

bundle exec rake

Miscellaneous

Copyright (c) 2009-2023 Dittmar Krall (www.matiq.com), released under the MIT license.