Prefix / Suffix feature
This PR introduces an improvement for utilizing prefix and suffix options for attribute names in jsonb_accessor.
Why?
In some cases, it suffices to store certain information about orders in jsonb format when it's only needed for historical purposes or for generating invoices, etc.
Results
How it currently looks in the Order model:
class Order < ApplicationRecord
jsonb_accessor :contact_details,
contact_details_email: [:string, { store_key: :email }],
contact_details_last_name: [:string, { store_key: :last_name }],
contact_details_first_name: [:string, { store_key: :first_name }],
contact_details_phone_number: [:string, { store_key: :phone_number }]
jsonb_accessor :billing_details,
billing_details_email: [:string, { store_key: :email }],
billing_details_last_name: [:string, { store_key: :last_name }],
billing_details_first_name: [:string, { store_key: :first_name }],
billing_details_phone_number: [:string, { store_key: :phone_number }],
billing_details_full_address_line: [:string, { store_key: :full_address_line }]
jsonb_accessor :shipping_details,
shipping_details_full_address_line: [:string, { store_key: :full_address_line }]
end
How it will look after implementing prefixes:
class Order < ApplicationRecord
jsonb_accessor :contact_details,
email: [:string, { prefix: true }],
last_name: [:string, { prefix: true }],
first_name: [:string, { prefix: true }],
phone_number: [:string, { prefix: true }]
jsonb_accessor :billing_details,
email: [:string, { prefix: true }],
last_name: [:string, { prefix: true }],
first_name: [:string, { prefix: true }],
phone_number: [:string, { prefix: true }],
full_address_line: [:string, { prefix: true }]
jsonb_accessor :shipping_details,
full_address_line: [:string, { prefix: true }]
end
In rails console:
order.billing_details #=> {"email"=>"[email protected]", "last_name"=>"Mosciski", "first_name"=>"Chi", "phone_number"=>"475912955432", "full_address_line"=>"Suite 897 932 Nitzsche Shoal, Tammiborough, MI 90349-4217"}
order.billing_details_first_name #=> "Chi"
order.contact_details_first_name #=> "Marquita"
order.contact_details #=> {"email"=>"[email protected]", "last_name"=>"Wunsch", "first_name"=>"Marquita", "phone_number"=>"278587369859"}
order.update(contact_details_first_name: "Mike", billing_details_first_name: "John") #=> true
order.reload.billing_details #=> {"email"=>"[email protected]", "last_name"=>"Mosciski", "first_name"=>"John", "phone_number"=>"475912955432", "full_address_line"=>"Suite 897 932 Nitzsche Shoal, Tammiborough, MI 90349-4217"}
order.reload.contact_details #=> {"email"=>"[email protected]", "last_name"=>"Wunsch", "first_name"=>"Mike", "phone_number"=>"278587369859"}
order.contact_details_last_name="Marsk" #=> "Marsk"
order.contact_details_last_name #=> "Marsk"
order.contact_details #=> {"email"=>"[email protected]", "last_name"=>"Marsk", "first_name"=>"Mike", "phone_number"=>"278587369859"}
irb(main):012:0> order.save #=> true
irb(main):013:0> Order.contact_details_where(last_name: "Marsk") #=>
[#<Order:0x00007f7a0a85fc88
id: 2,
contact_details: {"email"=>"[email protected]", "last_name"=>"Marsk", "first_name"=>"Mike", "phone_number"=>"278587369859"},
billing_details: {"email"=>"[email protected]", "last_name"=>"Mosciski", "first_name"=>"John", "phone_number"=>"475912955432", "full_address_line"=>"Suite 897 932 Nitzsche Shoal, Tammiborough, MI 90349-4217"},
shipping_details: {"full_address_line"=>"38266 Durgan Motorway, Kulasmouth, NC 46602-3316"},
contact_details_email: "[email protected]",
contact_details_last_name: "Marsk",
contact_details_first_name: "Mike",
contact_details_phone_number: "278587369859",
billing_details_email: "[email protected]",
billing_details_last_name: "Mosciski",
billing_details_first_name: "John",
billing_details_phone_number: "475912955432",
billing_details_full_address_line: "Suite 897 932 Nitzsche Shoal, Tammiborough, MI 90349-4217",
shipping_details_full_address_line: "38266 Durgan Motorway, Kulasmouth, NC 46602-3316">]
irb(main):014:0> Order.contact_details_where(last_name: "Random") #=> []
Also, it works fine with store key:
class Order < ApplicationRecord
jsonb_accessor :contact_details,
email: [:string, { prefix: true, store_key: :e }], # Added store key
last_name: [:string, { prefix: true }],
first_name: [:string, { prefix: true }],
phone_number: [:string, { prefix: true }]
jsonb_accessor :billing_details,
email: [:string, { prefix: true }],
last_name: [:string, { prefix: true }],
first_name: [:string, { prefix: true }],
phone_number: [:string, { prefix: true }],
full_address_line: [:string, { prefix: true }]
jsonb_accessor :shipping_details,
full_address_line: [:string, { prefix: true }]
end
order.contact_details #=> {"e"=>"[email protected]", "last_name"=>"Kshlerin", "first_name"=>"Jessie", "phone_number"=>"567805903539"}
irb(main):021:0> order.contact_details_email="[email protected]" #=> "[email protected]"
order.contact_details_email #=> "[email protected]"
order.contact_details #=> {"e"=>"[email protected]", "last_name"=>"Kshlerin", "first_name"=>"Jessie", "phone_number"=>"567805903539"}
order.save #=> true
order.reload.contact_details #=> {"e"=>"[email protected]", "last_name"=>"Kshlerin", "first_name"=>"Jessie", "phone_number"=>"567805903539"}
For quering you can use name attribute without prefix/suffix:
Order.find(7).contact_details => {"e"=>"[email protected]"}
Order.contact_details_where(email: "[email protected]") # =>
[#<Order:0x00007f96bdd7a158
id: 7,
contact_details: {"e"=>"[email protected]"},
billing_details: nil,
shipping_details: nil,
created_at: Sun, 07 Apr 2024 11:15:37.782726000 UTC +00:00,
updated_at: Sun, 07 Apr 2024 11:15:37.782726000 UTC +00:00,
contact_details_email: "[email protected]",
contact_details_last_name: nil,
contact_details_first_name: nil,
contact_details_phone_number: nil,
billing_details_email: nil,
billing_details_last_name: nil,
billing_details_first_name: nil,
billing_details_phone_number: nil,
billing_details_full_address_line: nil,
shipping_details_full_address_line: nil>]
I'll be happy if we have this improvement in this gem. Thank you!
fixes #173
Previous conversation is in this pull request: https://github.com/madeintandem/jsonb_accessor/pull/177
I'm not sure why CI is broken, don't think it's related to your change. I'll try to clean up the CI tomorrow :crossed_fingers:
I still need to get some additional permissions from @darcygarrett so I can't merge #183 right now, hopefully I should get them soon :crossed_fingers:
@mmarusyk I like the idea, but having to specify prefix: true for every field doesn't strike me as good. I'd rather have something like
class Order < ApplicationRecord
jsonb_accessor :contact_details, prefix: true,
email: :string,
last_name: :string,
first_name: :string,
phone_number: :string
jsonb_accessor :billing_details, prefix: true,
email: :string,
last_name: :string,
first_name: :string,
phone_number: :string,
full_address_line: :string
jsonb_accessor :shipping_details,
full_address_line: :string
end
Since the signature for the macro is def jsonb_accessor(jsonb_attribute, field_types) I think we can add keyword arguments there (especially since we're targeting Ruby 3.2+), but I'm not sure how well it'll play with the implicit Hash being passed for the field_types. Or do you have any other suggestion how we'd be able to DRY it up further?
@ashkulz Thanks for the feedback.
I agree that repeating prefix: true for every accessor isn't ideal. When I have time, I'll investigate ways to improve the macro, possibly by supporting keyword arguments.