How to create a multi level document using MongoId - ruby-on-rails

I am developing a Ruby on Rails (3.2.6) application and is using MongoId (3.0.0) to interact with the MongoDB database. I am just wondering how do save embeded JSON objects that contains multiple levels and not just one level.
I got an old MongoDB database with this and simular structure so I need to save new documents using the same structure.
This is from the documentation and is used to add a one level document:
Person.create(
first_name: "Heinrich",
last_name: "Heine"
)
How can I add an object with this structure:
{
"basic": {
"file_id": {
"file": "cf1952761a806c56c9bee60665418f02c"
},
"share": false,
"status": "created"
},
"data": {
"id": "4fd942dder5f5e88837300026e",
"name": "roberta",
"comment": "This is a comment"
}
}

The easiest way to do this is to create classes for basic and data and embed them in your top level document.
Embedded document classes are defined in Mongoid the same way as other documents with an embedded_in call and a matching embeds_one or embeds_many in the top level document.
The other option is to simply define a Hash field, but this obviously may have any structure.
Class Person
include Mongoid::Document
field :data, :type => Hash
...
end
:data will accept any hash, even with nested hashes.

Related

Rails Active Storage REST API

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

strong params JSON API specification

I am working to an REST API in Rails 5.2 and I'm following the JSON API specification. My JSON are like this:
{
"data":{
"attributes":{
"name":"Gleydson",
"age":"30"
},
"relationships":{
"occupation":{
"data":{
"attributes": {
"name": "Teacher",
}
}
}
}
}
}
I have the following method user_params:
params.require(:data).require(:attributes)
.permit(:name, :age,
relationships: { occupation: { data: { attributes: [:name] } } })
but when I print the return of the method nothing is returned
Have you looked into using a gem to go from Ruby objects to JSON:API serialized ones. JSONAPI::Resources helps a lot with getting the params right since you just define a JSONAPI:Resource and tell it what to allow.
I think part of your problem is that in JSON:API the attributes and relationships keys are siblings
user_params: {data:{attributes:{name:x,age:y,relationships:{z}}}}
jsonapi is: {data:{attributes:{name:x,age:y},relationships:{z}}}
In the JSON API spec "attributes" and "relationships" are siblings.
In your example you have nested "relationships" inside of "attributes".
Instead try:
params.require(:data).permit({
attributes: [:name, :age],
relationships: {
occupation: {
data: {
attributes: [:name]
}
}
}
})
According to the spec, attributes should not show up under a relationship's data member. A relationship's data member is a resource identifier object, meaning it only (and must only) contain type and id.
For JSON:API compliance checking, you may want to check out the new ruby gem easy-jsonapi. It has a middleware and response validator that is super easy to use with intuitive error messages to tell you why your documents, requests, and responses are not compliant. It also pairs well with JSONAPI-SERIALIZER, which as #robertoplancarte was suggesting, could take your ruby objects and serialize them into JSON:API compliant JSON documents.

Rails API Design: best way to include other attributes with json_api relationships

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)

How to get nested JSON / hash elements using postgres_ext-serializers?

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.

Getting couchrest and couch_potato to recognize existing couchdb documents

I'm trying to create a basic Rails CRUD app against a CouchDB database hosted on Cloudant.
I'm using couch_potato as my persistence layer and have it connecting properly to my Cloudant database.
The issues I'm having is my first model won't see the existing documents in my CouchDB database, unless I add a ruby_class field that equals the name of my model.
My simple User model:
class User
include CouchPotato::Persistence
property :id, :type => Fixnum
property :FullName, :type => String
view :all, :key => :FullName
end
Sample CouchDB document:
{
"_id": 123456,
"_rev": "4-b96f36763934ce7c469abbc6fa05aaf3",
"ORGID": 400638,
"MyOrgToken": "19fc342d50f9d8df1ecd5e5404f5e5f7",
"FullName": "Jane Doe",
"Phone": "555-555-5555",
"MemberNumber": 123456,
"Email": "jane#example.com",
"LoginPWHash": "14a3ccc0e6a50135ef391608e786f4e8"
}
Now, when I use my all view from the rails console, I don't get any results back:
1.9.2-p290 :002 > CouchPotato.database.view User.all
=> []
If I add the field and value "ruby_class: User" to the above CouchDB document, then I get results back in the console:
1.9.2-p290 :003 > CouchPotato.database.view User.all
=> [#<User _id: "123456", _rev: "4-b96f36763934ce7c469abbc6fa05aaf3", created_at: nil,
updated_at: nil, id: "123456", FullName: "Jane Doe">]
I'm working with a large set of customer data, and I don't want to write any scripts to add the ruby_class field to every document (and I may not be permitted to).
How can I get my app to recognize these existing CouchDB documents without adding the ruby_class field?
I couldn't find much documentation for couch_potato and couchrest that shows how to work with existing CouchDB databases. Most of the examples assume you're starting your project and database(s) from scratch.
Thanks,
/floatnspace
when you are looking at the all view of your User you will see something like ruby_class == 'User' so unless you add this property to your documents you will need to work around what couch_potato provides. you could i.e. use couch_rest directly to retrieve your documents, but i don't think that this what you want.
if you start persisting or updating your own documents, couch_potato will add the ruby_class field anyways. so i think the simples solution would be to just add them there.
another thing you can do is create a view that emits the documents also when they DON'T have the property set. this approach will only work if you have just one kind of document in your couchdb:
if(!doc.ruby_class || doc.ruby_class == 'User') {
emit(doc);
}

Resources