Association integration in a REST API with Rails and Angular 2/4 - ruby-on-rails

I'm working in a web application with Angular 2/4 on client side and a Rails application on the server side. In the rails side I have a model Product that has the following serializer:
class ProductSerializer < ActiveModel::Serializer
attributes :id, :weight
belongs_to :brand
end
This serializer generates:
{
"id": 1,
"weight": 62.0,
"brand": {
"id": 1,
"name": "foo"
}
}
Then, in Angular I have this class to parse the JSON:
export class Produto implements Model {
id: number;
weight: number;
brand: Brand;
}
Until here is everything ok, the issue began when the user alter the Product and the Angular has to send the Product altered to update route in Rails. If I just send the TypeScript object in the body of PUT or POST, the generated JSON will have the same pattern that the one received from Rails. But my rails app was expecting the following pattern:
{
"id": 1,
"weight": 62.0,
"brand_id": 1
}
In the ProductController I have the method product_params that I did this workaround to accept the pattern sended by Angular:
def produto_params
params[:brand_id] = params[:brand][:id] if params[:brand][:id]
params.delete(:brand) if params[:brand]
params.permit(:weight, :brand_id)
end
All said, this looks ugly and not scalable at all. This is just one association, if my product scale to 5 association this will grow big. Besides that, I can have many others situations like that with others models.
I think that I'm doing structure wrong, but where is wrong and where is right is obscure to me, so I need some help to structure in a scalable way.
Some considerations: Both Angular app and Rails app are mine and they are in early development, so I can do structural changes with few troubles. I learning Ruby, Rails, TypeScript and Angular with this project, but I'm trying to reuse code the maximum I can, so what want to achieve is a structure that I don't need to write serializer for every model in both sides. A approach that thought was a generic serializer in Angular, using decoratos in the fields that carry object from association, so overriding the toJSON() method I would transform a brand in a brand_id (don't know if is even possible), this would be a bad approach?
Thanks in advance.

Related

Get user attributes using user_id - Rails w/ React

[EDIT] I am using the React and Rails differently and not using the react-rails gem...
Ok so i have my api written in Rails and it is formatted like this:
data: {
comments: [
{
comment: 'Lorem Ipsum',
user_id: 1
},
{
comment: 'dolor sit',
user_id: 2
},
{
comment: 'amet',
user_id: 3
}
]
}
now, in my react view, i want to have somewhat like, <%= User.find_by_id(params[:id]).name %>. What am I thinking is just to add posted_by attribute to table and add it as a key in api, having the string value of the user_id already set up with the controller(rails approach) and another one is to write another request(react approach) that will just fetch the name but i think its too much. What is the better approach?
In my opinion, it really depends on the numbers. You don't want to make, say, 15 requests if you have 15 comments on that page. My general proposing would be to keep this as close to JSON API as possible. However, if you want to stick to your current case, I would have two datasets:
1.Comments - which would be something like you have already
2.Users - Array of users, with all required fields (like name).
So that you would do only two requests initially.
Ok so this might be late. I just added a relationship between user and comments and added posted_by: Comment.user.name in the json response

Sending object as a variable to Mandrill via Rails

I'm converting an email template from Rails to Mandrill, the content for which requires a fair amount of data, some of which is nested through several associations.
Therefore, I'd like to pass objects via Mandrill's global_merge_vars, such as the (simplified) following:
[{ 'name'=>'order', 'content'=> #order.to_json(include:
{ user: { only: :first_name } },
methods: [:method1,
:method2,
:method3,
:method4])
}]
Which passes through to the mandrill template under the order variable similar to the following:
{"id":11,"number":"xxxx","item_total":"112.0"...
"user":{"first_name":"Steve"},"method1":"£0.00","method2":"£112.00",
"method3":"£112.00","method4":"£0.00"}
The problem is, I can't access anything within order (using Handlebars), i.e. {{order.id}}, {{order['id']}} etc. wont work.
It's not an option to break out data into a large number of variables, as some elements are collections and their associations.
I believe the problem occurs as everything is stringified when the variables are compiled for Mandrill -- therefore breaking the JSON object -- with the following a snippet of what is sent across:
"global_merge_vars"=>[{"name"=>"order", "content"=>"{\"id\":11,
\"number\":\"xxxx\",\"item_total\":\"112.0\"...
I can't seem to find any documentation / suggestions for dealing with this, so am wondering whether this it's possible to pass data of this nature, and, if so, how to correctly pass it to be able to access the objects in the Mandrill template. Any advice greatly appreciated!
Steve.
try this:
[{ 'name'=>'order', 'content'=> JSON.parse(#order.to_json(include:
{ user: { only: :first_name } },
methods: [:method1,
:method2,
:method3,
:method4]))
}]

Calculating the Count of a related Collection

I have two models Professionals and Projects
Professionals hasMany Projects
Projects belongsTo Professionals
In the Professionals index page i need to show the number of projects the Professional has.
Right now i am doing the following query to get all the Professionals.
How can i fetch the count of the Projects of each of the Professionals as well.
#pros = Professionals.all.asc(:name)
I would add projects_count to Professional
Then
class Project
belongs_to :professional, counter_cache: true
end
And rails will handle the count every time a project is added to or removed from a professional. Then you can just do .projects_count on each professional.
Edit:
If you actually want additonal data
#pros = Professionals.includes(:projects).order(:name)
Then
#pros.each do |pro|
pro.name
pro.projects.each do |project|
project.name
end
end
I am just abstracting here because the rails thing really isn't my bag. But let's talk about schema and things to look for. And as such the code is really just "pseudo-code" but should be close to what is wanted.
Considering "just" how MongoDB is going to store the data, and that you presumably seem to have multiple collections. And I am not saying that is or is not the best model, but just dealing with it.
Let us assume we have this data for "Projects"
{
"_id" : ObjectId("53202e1d78166396592cf805"),
"name": "Project1,
"desc": "Building Project"
},
{
"_id" : ObjectId("532197fb423c37c0edbd4a52")
"name": "Project2",
"desc": "Renovation Project"
}
And that for "Professionals" we might have something like this:
{
"_id" : ObjectId("531e22b7ba53b9dd07756bc8"),
"name": "Steve",
"projects": [
ObjectId("53202e1d78166396592cf805"),
ObjectId("532197fb423c37c0edbd4a52")
]
}
Right. So now we see that the "Professional" has to have some kind of concept that there are related items in another collection and what those related items are.
Now I presume, (and it's not my bag) that there is a way to get down to the lower level of the driver implementation in Mongoid ( I believe that is Moped off the top of my head ) and that it likely ( from memory ) is invoked in a similar way to ( asssuming "Professionals" as the class model name ) :
Professionals.collection.aggregate([
{ "$unwind": "$projects" },
{ "$group": {
"_id": "$_id",
"count": { "$sum": 1 }
}
])
Or in some similar form that is more or less the analog to what you would do in the native mongodb shell. The point being, with something like this you just made the server do the work, rather than pulling all the results to you client and looping through them.
Suggesting that you use native code to iterate results from your data store is counter productive and counter intuitive do using any kind of back end database store. Whether it by a SQL database or a NoSQL database, the general preference is as long as the database has methods to do the aggregation work, then use them.
If you are writing code that essentially pulls every record from your store and then cycles through to get the result then you are doing something wrong.
Use the database methods. Otherwise you might as well just use a text file and be done with it.

The "right" way to create a new backbone-relational model instance in a hasMany relation

I'm a Rails guy trying to become a Rails API server + Backbone front end guy, and I have a strong feeling I'm not doing something right with respect to creating new instances of a model inside a collection contained in another object.
In all the below I'm using backbone-relational. FWIW I'm also using Marionette but I don't think that matters much here.
To get specific, say that I have an Engine model, and that Engines can have many Gears. I am converting my Engine edit page in the old Rails code to a little Backbone app. My new Backbone Engine edit page looks like this:
When I click the "Add New Gear" button, I would ideally like the outcome to be (1) that the server creates (persists) a new blank Gear object connected to the Engine and (2) that the a new backbone Gear instance gets added that is synched to the new server state.
In a purely Rails world, I could POST to a URL like:
api/engines/42/gears
and that would create the new Gear instance on the server side and I would redirect to some page. I just don't get how to do that on the client side and have everything be synched up in the end.
Here are the Backbone (Relational) models for Engine, Gear, and the Gears collection:
class Engine extends Backbone.RelationalModel
urlRoot: '/api/engines'
relations: [
{
type: Backbone.HasMany,
key: 'gears',
relatedModel: 'Gear'
collectionType: 'Gears',
reverseRelation: {
key: 'engine',
includeInJSON: false
}
}
]
Engine.setup() // here b/c I'm using coffeescript
Here's the Gears collection:
class Gears extends Backbone.Collection
model: Gear
And here's the Gear model:
class Gear extends Backbone.RelationalModel
urlRoot: '/api/gears'
Gear.setup() // here b/c I'm using coffeescript
I can create a new backbone Gear object with newGear = new Gear(), but how do I connect it into the Engine's list? If I say myEngine.get('gears').add(newGear), what am I supposed to save?
I'm hoping I can somehow just save (POST) the new gear without calling save() on the Engine instance. Trying to update the engine instance (myEngine.save() after the above) doesn't really seem to do the trick anyway because the new Gear instance doesn't yet have an ID so the Engine update call freaks out.
If anyone can steer me in the right direction I'd appreciate it!
And sorry for anyone who ended up here looking for information about Rails Engines... bad example name choice :-)
I was able to figure this out. Writing a SO question really helps to focus the mind :-)
The final code to add the Gear to the Engine is pretty simple:
gear = new Gear()
myEngine.get('gears').create(gear)
I had tried this before (actually I had the second line split as an add(...) followed by a save() call) but it wasn't working. There were multiple reasons for this, the biggest of which was my setup of the HasMany relation. Here's the updated relation for the Engine class:
class Engine extends Backbone.RelationalModel
urlRoot: '/api/engines'
relations: [
{
type: Backbone.HasMany,
key: 'gears',
relatedModel: 'Gear'
collectionType: 'Gears',
reverseRelation: {
key: 'engine',
keySource: 'engine_id', # wasn't here before
includeInJSON: 'id' # used to be false
}
}
]
keySource and includeInJSON together let me specify how I want the engine represented in the gear's JSON output. Earlier, when I just had the default value for includeInJSON, true, I was getting the entire engine serialized in gear's JSON output, which I didn't want. So I changed it to false. But in the POST that eventually worked to send the new Gear object to the server to be created, I really needed the engine foreign key so that the Rails server could hook up the new model correctly. If I switched includeInJSON to 'id' so that the serialization had {engine: <id value>} that was better (except that for Rails I really wanted {engine_id: <id value>}. BUT, with this setup whenever I said myGear.get('engine') I got the ID of the engine, when I really wanted the whole model. The discovery of keySource lets me send {engine_id: <id value>} out to the API server while still letting my get the engine model through myGear.get('engine').
Then in my Rails API I can take the engine_id and create the appropriate blank Gear, which then gets spit back to the client as JSON, which then updates the view.
If anyone else has any insights into this whole situation, I'd of course still be happy to hear them.

Generate ruby classes from json document

Consuming a ruby json API, I want to save me some work and generate ruby objects off the bat. Any way to do this?
so you could transform this:
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
to this:
class Menu
attr_accessor :id
attr_accessor :file
attr_accessor :popup
end
If you're looking to turn a JSON string into a Ruby Hash you can do
my_hash = JSON.parse('{"foo":"bar"}')
puts my_hash['foo']
There is a wonderful gem for doing this. https://github.com/apotonick/representable/
Here's what your representable would look like
module MenuRepresenter
include Representable::JSON
property :id
property :value
property :popup
end
Create your model
class Menu
attr_accessor :id, :value, :popup
end
menu = Menu.new.extend(MenuRepresenter).from_json(json)
# You can convert it back into json via .to_json
# expect(menu.to_json).to eq(json)
The example above shows only the basic implementation, you would want to create another class for the menu item, take a look at the documentation at the github repo for more detailed information.
If you want "methodized" hashes which use accessor methods (not a bad idea at all!)
require "ostruct"
object_like_hash = OpenStruct.new(some_hash_with_string_or_symbol_keys)
object_like_hash.foo #=> same as some_hash["foo"]
Nonexistent keys will return nil and not raise unfortunately.
I think you are a little bit confused. In the question, you ask how to turn a JSON document into classes. In the comments, you say you want a JSON version of the RXSD XML tool, which however, turns XML schemas into Ruby classes.
Turning JSON documents into classes doesn't really make sense. If you compare the world of document markup to programming, documents correspond to objects and schemas correspond to classes (well, types, actually, but since we're talking about Ruby, let's not open that can of worms and stick with classes).
So, it makes sense to generate Ruby objects from JSON documents and it makes sense to generate Ruby classes from JSON schemas, but it doesn't make sense to generate Ruby classes from JSON documents. The bad news is of course that in order to be able to automatically generate Ruby classes from JSON schema is that in order for that to work, the JSON schema has to be in an automatically processable (IOW machine-readable) format.
Unfortunately, there is no such thing as a JSON schema, and thus JSON schemas tend to generally not be machine-readable, but rather are just a blurb of human-oriented English text on the API documentation page of the web service provider. If you're lucky. More often than not, there is no documentation at all about the JSON schema.
So, since there is no standardized way of describing JSON schemas, there cannot be a standardized tool for processing JSON schemas. Unlike XML, where there is a limited number of standardized schemas (DTD, XSD, RelaxNG).
Note that what I wrote above is not strictly true: there are specifications for JSON schemas (e.g. JSON-Schema) and there are Ruby implementations of those (e.g. Ruby/JSONSchema, validation only, doesn't generate classes), but nobody is using them, so they might just as well not exist.

Resources