I have a simple controller that is hit by webhooks. I need to store all data sent in a model's metadata
which is a text
column for later consumption.
class NotificationsController < ApplicationController
def create
notification = Notification.new(
metadata: params,
)
if notification.save
head :ok
end
end
end
When I inspect params.class
inside any controller action, I get an ActionController::Parameters
object that acts like a hash.
However, when storing params
in the metadata
as shown above, I get a string that looks like this:
"{\"SmsSid\"=>\"ID\", \"SmsStatus\"=>\"STATUS\",\"controller\"=>\"notifications\", \"action\"=>\"create\"}"
I tried converting that string back to a hash, but doing JSON.parse(params)
throws the following error:
JSON::ParserError: 783: unexpected token at "{\"SmsSid\"=>\"ID\", \"SmsStatus\"=>\"STATUS\",\"controller\"=>\"notifications\", \"action\"=>\"create\"}"
Is this because the column type is text
and not jsonb
? If so, is there any workaround that does not involve a DB migration to change the column type?
CodePudding user response:
When I inspect params.class inside any controller action, I get an ActionController::Parameters object that acts like a hash.
Yes, this is what params
is. It's an object, that acts like a hash
in most respects.
However, when storing params in the metadata as shown above, I get a string that looks like this:
"{\"SmsSid\"=>\"ID\", \"SmsStatus\"=>\"STATUS\",\"controller\"=>\"notifications\", \"action\"=>\"create\"}"
Yes, because you're asking Rails to take something that is not a string, and convert it to a string so it can be stored in a text
column. Rails does this by calling .to_s
on the object, which returns the string representation you're seeing here.
I tried converting that string back to a hash, but doing JSON.parse(params) throws the following error:
That string isn't JSON. If you want to serialize the params
hash to JSON, you can use the serialization API to define how the column should be serialized, and then save some safe subset of params:
class Notification < ActiveRecord::Base
serialize :metadata, JSON
end
...
notification = Notification.new(
metadata: params.permit(:SmsSid, :SmsStatus),
)
Afterwards, you can access notification.params
and it will be transparently deserialized for you.
Note that simply dumping the entire unsanitized params
object into your database is a great way to allow attackers to flood your database with gigabytes of garbage text. You should never assume that params
is safe; only work with a subset of params
that you expect, and make sure the associated values conform to your expectations. Simply using params.permit(...)
as I've done above is still not sufficient, as the values could be extremely long, or contain arbitrary garbage. You should use validators on your model to enforce length and format restrictions.
CodePudding user response:
I think storing the data as json string can solve the problem. like this:
class NotificationsController < ApplicationController
def create
notification = Notification.new(
metadata: params.to_json,
)
if notification.save
head :ok
end
end
end
Then:
JSON.parse(Notification.first.metadata)
or you can override the metadata getter method if don't want to write the above code everywhere
class Notification < ApplicationRecord
def metadata
JSON.parse(self[:metadata])
end
end