kgiszczak / shale

Shale is a Ruby object mapper and serializer for JSON, YAML, TOML, CSV and XML. It allows you to parse JSON, YAML, TOML, CSV and XML data and convert it into Ruby data structures, as well as serialize data structures into JSON, YAML, TOML, CSV or XML.
https://shalerb.org/
MIT License
626 stars 19 forks source link

Idea for better rails/ActiveRecord integration #27

Closed bkjohnson closed 12 months ago

bkjohnson commented 12 months ago

I've been trying to see if shale would be a good choice for a rails app I'm working on. I saw in https://github.com/kgiszczak/shale/issues/16 that you pointed to the Custom Model documentation which seems helpful, but the downside is that in order to use that developers have to redefine the attributes that are already defined in schema.rb.

If I set up a very basic rails app with a schema that looks like this:

(Created a migration with rails generate migration CreatePerson first_name:string last_name:string address:string)

ActiveRecord::Schema[7.0].define(version: 2023_10_05_210646) do
  create_table "people", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.string "address"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

Then I'm able to call Person.attribute_types and get something that has the type for each attribute key along with other schema-adjacent data:

irb(main):001> Person.attribute_types
=> 
{"id"=>
  #<ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer:0x00007f455dd81cc0
   @limit=nil,
   @precision=nil,
   @range=-9223372036854775808...9223372036854775808,
   @scale=nil>,
 "first_name"=>
  #<ActiveModel::Type::String:0x00007f455dd81310
   @false="f",
   @limit=nil,
   @precision=nil,
   @scale=nil,
   @true="t">,
 "last_name"=>
  #<ActiveModel::Type::String:0x00007f455dd81310
   @false="f",
   @limit=nil,
   @precision=nil,
   @scale=nil,
   @true="t">,
 "address"=>
  #<ActiveModel::Type::String:0x00007f455dd81310
   @false="f",
   @limit=nil,
   @precision=nil,
   @scale=nil,
   @true="t">,
 "created_at"=>#<ActiveRecord::Type::DateTime:0x00007f455dd413f0 @limit=nil, @precision=6, @scale=nil>,
 "updated_at"=>#<ActiveRecord::Type::DateTime:0x00007f455dd413f0 @limit=nil, @precision=6, @scale=nil>}

So I'm wondering if it would be feasible for shale to use that data so that instead of having to redefine attributes in our mapper like this:

class PersonMapper < Shale::Mapper
  model Person

  attribute :first_name, Shale::Type::String
  attribute :last_name, Shale::Type::String
  attribute :address, AddressMapper
end

we could do something like this and let shale create the attributes based on what's already defined on the model:

class PersonMapper < Shale::Mapper
  model Person

  attributes Shale::Schema.from_active_model(Person)
end
kgiszczak commented 12 months ago

Hey Brooks, thanks for suggestions, but I want to keep the scope of this project limited. Integrations with other projects is probably better put in external gems.

Also integration with rails is so trivial I don't think it needs specific treatment. All you need is a mapping from rails to shale types. Here's an example:

RAILS_TO_SHALE_TYPE_MAPPING = {
  integer: Shale::Type::Integer,
  string: Shale::Type::String,
  datetime: Shale::Type::Time,
}

class PersonMapper < Shale::Mapper
  model Person

  Person.attribute_types.each do |name, att|
    attribute name.to_sym, RAILS_TO_SHALE_TYPE_MAPPING[att.type]
  end
end
bkjohnson commented 12 months ago

That makes sense, thank you for the feedback! I'll work on a solution like your example.