I'm trying to get the postgres_ext-serializers gem working, and I built a test project very similar to https://github.com/dockyard/postgres_ext-serializers/blob/master/test/test_helper.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :mobile
embed :ids, include: true
has_one :address, serializer: AddressSerializer
def include_mobile?
false
end
alias_method :include_address?, :include_mobile?
end
class AddressSerializer < ActiveModel::Serializer
attributes :id, :district_name
embed :ids, include: true
end
When I try to run the serializers the output doesn't seem to have nested elements. For example my serializer to_json output is:
"{\"users\":[{\"id\":1,\"name\":\"Aaron\",\"mobile\":null}, \n {\"id\":2,\"name\":\"Bob\",\"mobile\":null}],\"addresses\":[{\"id\":1,\"district_name\":\"Rob's Address\"}]}"
Notice how users and address are two separate elements of a hash, intead of being nested. If I remove the postgres_ext-serializers gem, then the output is as expected:
"[{\"id\":1,\"name\":\"Rob\",\"mobile\":null,\"address\":{\"id\":1,\"district_name\":\"Rob's Address\"}},{\"id\":2,\"name\":\"Bob\",\"mobile\":null,\"address\":null}]"
The address is embedded in the user hash exactly how I'm expecting it.
What am I missing, do I need to change anything to make the elements nested when using postgres_ext-serializers?
Thanks!
It seems, that the JSON you receive after serialization is what postgres_ext-serializers expects. Take a look into this test. expected_json in the first test case case is:
{
"users": [
{
"id": <UserID>,
"name": "John",
"mobile": "51111111",
"offer_ids": [],
"reviewed_offer_ids": []
}
],
"offers": [],
"addresses": [
{
"id": <AddressID>,
"district_name": "mumbai"
}
]
}
It looks very similar to the JSON you have received. But, to be honest, as include_address? method in your example returns false, I expect you must have not "addresses" field included into resulting JSON at all.
Related
In my Rails 7 app I'm receiving request from the external API. I want to check if the incoming request is valid. To do so I've to use strong parameters, here is the sample JSON request which hit my endpoint:
"sdd_request": {
"return_url": "https://example.com/return",
"data": {
"debit_method": "CORE",
"debtor": {
"name": "John Doe",
"email": "john.doe#example.com",
}
},
"extension": {
"signees": [{email: 'test#test.com', name: 'joe', last_name: 'smith' }],
"creditor": {
"id": "12345",
"name": "Acme Inc.",
"address": {
"city": "New York",
}
}
}
}
}
So the required are:
- return_url
- data
- signees
- creditor
How to require above parameters?
What I did was:
def sdd_setup_request_params
params.require(:sdd_request).permit(
:return_url,
data: [
:debit_method,
debtor: [
:name,
:email,
]
],
extension: [
signees: [],
creditor: [
:id,
:name,
address: [
:city,
]
]
]
]
)
end
But from what I understand that required whole object of sdd_request not individual components because if e.g. signees are missing it will nor raise an error of missing parameters am I right?
The role of strong parameters is not to validate the data. It's just to whitelist the parameters to avoid mass assignment vulnerabilities.
require is used to bail early if the general structure of the parameters makes it pointless to continue processing the request. Like for example in your typical Rails controller:
params.require(:person)
.permit(:name, :age, :city)
There is no point in continuing to process the request if the key :person is missing so a ActionController::ParameterMissing exception is raised which Rails rescues and returns a 400 - Bad Request response.
This prevents the potential nil errors that could occur when you expect a hash and get nil instead.
Validating the presence of the actual attributes is the job of the model in Rails.
class Person
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name
attribute :age
attribute :city
validates :name, :age, :city, presence: true
end
You might be thinking now: But waaaah I don't have a model because I'm gettting it from an API and I'm not saving it. Models can still be really useful even without persistence since the represent the entities in your application in a normalized form.
If you still really want to avoid a model for whatever reason there are gems that provide model-less validation in the controller with a bit more grace then a web of if statements.
While you can use require on nested hash structures like JSONAPI.org this will just return a single key and will raise on the first missing key so it cannot be used to give meaningful feedback about whats wrong with the input.
params.require(:data)
.require(:attributes)
.permit(:name, :age, :city)
I'm using the Rails ActiveModel Serializer to output JSON to an API endpoint.
I'm getting this JSON output:
{
"data":[
{
"id":"396",
"type":"profiles",
"attributes":{
"name":"Impossibles",
"created-at":"2017-05-11T18:14:06.201-04:00",
"updated-at":"2018-04-01T13:34:15.905-04:00",
"website":"http://soundcloud.com/impossibles"
}
}
]
}
But was expecting it to be formatted like this:
{
"data":[
{
"id":"396",
"type":"profiles",
"name":"Impossibles",
"created-at":"2017-05-11T18:14:06.201-04:00",
"updated-at":"2018-04-01T13:34:15.905-04:00",
"website":"http://soundcloud.com/impossibles"
}
]
}
Trying to avoid the extra level of nesting in the returned JSON.
Is there a way to remove the "attributes" key?
This is my serializer:
class ProfileSerializer < ActiveModel::Serializer
attributes :id, :name, :created_at, :updated_at, :website
end
And my controller:
def show
profile = Profile.find(params[:id])
render json: profile, status: :ok
end
After reading through some GitHub issues, it appears the "attributes" nesting is coming from the JSON API spec and is expected behavior:
https://jsonapi.org/format/#document-resource-objects
This issue was helpful:
https://github.com/rails-api/active_model_serializers/issues/2202
Looks like a feature, not a bug.
I've been able to set up Active Storage file uploads and now I'm trying to return associated images when I do, for instance, Speaker.all or Speaker.find(2).
Calling the associated endpoint I get something like:
{
"id": 2,
"name": "Rafael",
"email": "rafael.almeida#mail-provider.com",
"company": "XING",
"social_media": "{\"twitter\": \"#rafaelcpalmeida\"}",
"created_at": "2018-10-01T17:21:50.993Z",
"updated_at": "2018-10-01T17:21:51.144Z"
}
How can I also return its associated avatar?
I figured out what to do in order to achieve the result I wanted. First, we need to add the active_model_serializers to the Gemfile, followed by bundle install.
After we installed the gem we should add include ActionController::Serialization to every controller that's going to use the Serializer.
We generate a new serializer using rails g serializer speaker. My SpeakerSerializer looks like:
class SpeakerSerializer < ActiveModel::Serializer
attributes :id, :name, :email, :company, :avatar
def avatar
rails_blob_path(object.avatar, only_path: true) if object.avatar.attached?
end
end
And my output looks like
{
"speaker": {
"id": 2,
"name": "Rafael",
"email": "rafael.almeida#xing.com",
"company": "XING",
"avatar": "/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--515a0de8817b3529b5d3d168871cebf6ccee0463/xing-photo.jpg"
}
}
Check if this approach is good for your needs. This is the case of has_one_attached.
First, fetch the record:
speaker = Speaker.find(2)
Then convert it to a Ruby hash (please note .as_json):
speaker_hash = speaker.as_json
Now, just append the pair key-value you need, using url_for helper:
speaker_hash['url'] = url_for(speaker.avatar)
Finally, convert the hash to json:
speaker_hash.to_json
I have a Rails 5 app in which I use the gem active_model_serializers(https://github.com/rails-api/active_model_serializers). In my app I have a simplified data model that looks something like this:
# LocalizedString.rb
has_many :translations
# Translation.rb
belongs_to :localized_string
I'm trying to follow the best practices from JSON API, I have configured active_model_serializers like this:
ActiveModelSerializers.config.adapter = :json_api
When a user of the API requests translations (http://[root]/api/apps/117/translations) I currently get the following result:
{
"data": [
{
"id": "152",
"type": "translations",
"attributes": {
"value": "Test",
},
"relationships": {
"language": {
"data": {
"id": "1",
"type": "languages"
}
},
"localized-string": {
"data": {
"id": "162",
"type": "localized-strings"
}
}
}
},
[...]
From my localised-string I also want to include another attribute that is critical for the consumer of the API, and I don't want to have to make another API call to get the value of the attribute. I wonder what is the best / recommended way to do this that also follows json_api if possible.
Something like this could work:
"localized-string": {
"data": {
"id": "162",
"key": "my key value", # the attribute I need.
"type": "localized-strings"
}
}
But I'm not sure how to achieve that using active_model_serializers or if it is another recommended way of doing what I want with [json_api][1].
For completion, my relevant serialiser files looks lik this:
class TranslationSerializer < ActiveModel::Serializer
attributes :id, :value, :created_at, :updated_at
has_one :language
has_one :localized_string, serializer: LocalizedStringParentSerializer
end
class LocalizedStringParentSerializer < ActiveModel::Serializer
# I want to include the key attribute in my relationship object, but this doesn't work.
attributes :id, :key
end
So, any ideas on what I need to do to achieve what I want?
Per spec, relationships are represented by resource object identifiers. To include more than just the id and type, you'll want to use the include param. In AMS, I think that would be 'include: [:localizations], fields: { localizations: [:key]}' (not at computer now, but is approx right)
I am getting info from my database
#teams = Team.select(:id,:name)
Then, I am rendering this info with an assosiation
render json: #teams, include: :players
This produce me the following input:
[
{
"id": 1,
"name": "Lyon Gaming",
"players": [
{
"name": "Jirall",
"position": "Superior",
},
{
"name": "Oddie",
"position": "Jungla",
}
]
}
]
How ever I need to sort the players in an specific order (not alphabetical)
I already have the sql statement to do it; how ever I dont know how to apply this method to each association.
I am looking something like:
render json: #teams, (include: :players, method: custom_order)
But this raise me an error.
Any ideas?
This should work:
render json: #teams, include: :players, methods: :custom_order
I'd like to add that if you will always want to return the object this way, you can add this to your model instead:
def as_json(options = nil)
super(include: :players, methods: :custom_order)
end
As Adam has mentioned, active_model_serializers is good to have if your render json's start getting ugly. If the complexity doesn't grow too much, I always recommend keeping the dependencies down.
https://github.com/rails-api/active_model_serializers
You need to read more about serializers in rails(This is good practice)
Here you have a nice article about this https://www.sitepoint.com/active-model-serializers-rails-and-json-oh-my/
Then you can make something like this
app/serializers/team_serializer.rb
class TeamSerializer < ActiveModel::Serializer
attributes :id, :name, :ordered_players
end
app/controllers/teams_controller.rb
def index
respond_with(Team.all.map(&TeamSerializer.method(:new)))
end