Headless Rails, easy way to store a hash? - ruby-on-rails

I'm new to rails, but most of the documentation is towards user inputting something into the view and it eventually gets passed into the database.
Is there a rails way of storing below into a SQL database? Do I put it in the model or controller?
Is there a clean way to store this data, or do I have to explicitly store every attribute in this Hash individually?
I've already made the migrations manually that matches most if not all of the hashed data below, but is there a tool that can convert these hashes into a relational Data model?
.
{
"_id" : "36483f88e04d6dcb60684a33000791a6bc522a41",
"address_components" : [
{
"long_name" : "ON",
"short_name" : "ON",
"types" : [
"administrative_area_level_1",
"political"
]
},
{
"long_name" : "CA",
"short_name" : "CA",
"types" : [
"country",
"political"
]
},
{
"long_name" : "M5J 1L4",
"short_name" : "M5J 1L4",
"types" : [
"postal_code"
]
}
],
"formatted_address" : "ON, Canada",
"formatted_phone_number" : "(416) 362-5221",
"geometry" : {
"location" : {
"lat" : 43.640816,
"lng" : -79.381752
}
},
"icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
"id" : "36483f88e04d6dcb60684a33000791a6bc522a41",
"international_phone_number" : "+1 416-362-5221",
"name" : "Scandinavian Airlines",
"reference" : "CoQBcgAAAMobbidhAbzwIMkxq3GTHzzlEW4hAAwxg5EmGDP7ZOcJRwUK29poFMTDvED5KW9UEQrqtgTwESj_DuCAchy6Qe5pPZH9tB47MmmuQHvyHFlApunmU3MN05_KLekN5hEbrW7Gv2ys2oXmn7FpvD7-0N0QILlFXCiwL5UlYWo2sEg3EhBMBsrkHBu4WCFsMCHRqgadGhTM3BVWR15l9L87zL1uN1ssoW4WCw",
"types" : [
"restaurant",
"food",
"establishment"
],
"url" : "https://plus.google.com/100786723768255083253/about?hl=en-US",
"utc_offset" : -300,
"vicinity" : ""
}

This data structure can be stored by a matching hierarchy of models/associations.
There is a very clean way which is...
Use accepts_nested_attributes_for. This will work for your entire structure except for the 'types' arrays which contains simple lists of strings. However, you can use a workaround for this specific case.
The only thing that cannot be stored (easily) is the id. ActiveRecord won't permit you to set the id directly, as it is supposed to be an implementation detail of the backing database. In your case, you can simply borrow the _id field which seems to contain the same data and insert that into an alias of some sort.
Here is an example of the code you might use:
class Address < ActiveRecord::Base
has_many :address_components
has_many :address_types
has_one :geometry
attr_accessor :address_components_attributes, :geometry_attributes
accepts_nested_attributes_for :address_components, :geometry
def types=(types)
types.each do |t|
self.address_types << AddressType.build(name: t)
end
end
def _id=(orig_id)
self.original_id = orig_id
end
end
class AddressType < ActiveRecord::Base
belongs_to :address
end
class Geometry < ActiveRecord::Base
belongs_to :address
has_one :location
attr_accessor :location_attributes
accepts_nested_attributes_for :location
end
class Location < ActiveRecord::Base
belongs_to :geometry
end
class AddressComponent < ActiveRecord::Base
belongs_to :address
has_many :component_types
def types=(types)
types.each do |t|
self.component_types << ComponentType.build(name: t)
end
end
end
class ComponentType < ActiveRecord::Base
belongs_to :address_component
end
Now you can store the entire structure using:
Address.create(data_hash)

If you have setter methods on your model, they can handle the data and import from this hash as you would want.
For example, given the hash above, if you had a method:
def address_components=(ac)
# Handle address components
end
This will get called if when you do the following (assuming the name of your model is MyModel and the hash is stored in #hash).
MyModel.new(#hash)
All the keys will trigger setter methods of the structure 'key='. This is very powerful - if you have a very well structured model, but have an arbitrary hash, you can create methods that handle the keys in the hash. Based on these, you can build new objects, build associations and save it all at the same time.
Note - you may need to strip out some keys, or handle some keys that use reserved ruby terms in a custom way.

Related

How to hide created_at and updated_at when using ActiveModel::Serializer

I'm using active_model_serializers in rails application it's working perfectly fine, but when dealing with associations it's returning all the attributes of the associated model (including the created_at and updated_at) which I don't want to be returned.
class ReservationSerializer < ActiveModel::Serializer
attributes :id, :pnr_no, :train_no, :passenger_name, :from_to,
:travel_class, :cancelled, :travel_time
has_many :reservation_seats
end
...
attributes of reservation are returned, which are fine therefore only
including the relationship attributes i.e for reservation_seats
...
"relationships": {
"reservation-seats": {
"data": [
{
"id": 4,
"reservation-id": 5,
"seat-no": "26",
"position" : "2",
"created-at": "2017-05-27T23:59:56.000+05:30",
"updated-at": "2017-05-27T23:59:56.000+05:30"
}
]
}
I tried creating a new file as well, where I have defined the attributes which needed to returned, but in this case it's just returning type only.
class ReservationSeatSerializer < ActiveModel::Serializer
attributes :id, :seat_no, :position
belongs_to :reservation
end
this is resulting in:
"relationships": {
"reservation-seats": {
"data": [
{
"id": "4",
"type": "reservation-seats"
}
]
}
}
Basically for the association I only want few attributes to be returned.
Thanks
The JSON API spec wants you to reduce the response data and database requests by just including the type and identifier of the relation. If you want to include the related object, you have to include it:
ActiveModelSerializer Example:
render json: #reservation, include: '*'
This includes all the relationships recursively. These related objects will end up in the included array.
Take a look at the JSON API spec and the active_model_serializer docs.
You could try adding a serializer of your association inside of it:
class ReservationSerializer < ActiveModel::Serializer
attributes :id, :pnr_no, :train_no, :passenger_name, :from_to,
:travel_class, :cancelled, :travel_time
has_many :reservation_seats
class ReservationSeatSerializer < ActiveModel::Serializer
attributes :id, :seat_no, :position
end
end

How to create model with an array field which contains another documents as an embedded documents in Mongodb (Mongoid)

I am using Rails 4 with Mongoid for an event based application.
I am trying to create a model where I want to add an array field with embedded documents in that array. This embedded documents will contain user's geo coordinate and timestamp. After every 5 minutes I will be pushing user's latest coordinates to user's (location) array. can someone please help me, How can i create that.
My sample model and desired documents are as below.
class User
include Mongoid::Document
field :name, type: String
field :locations, type: Array
end
Here I want to push
Here is sample document that I am looking for as a result:
{ _id : ObjectId(...),
name : "User_name",
locations : [ {
_id : ObjectID(...),
time : "...." ,
loc : [ 55.5, 42.3 ]
} ,
{
_id : ObjectID(...),
time : "...",
loc : [ -74 , 44.74 ]
}
]
}
I was able to add the value in location array without embedded document through IRB, but as I will be using MongoDB's Geospatial queries later on, so I want to use 2D indexes and rest of the stuff Mongo Documentation mentioned.
Hence I believe it needs to have array of documents which contain the latitude & longitude. which will also save my time to code.
Also can I make the time of the location as documents '_id' ? (It can help me to reduce the query overhead)
I would really appriciate if someone can help me with the structure of model i should write or guide me to the references.
P.S: Let me know if you suggest some extra references/help about storing geospatial data in mongoDB which can be helpful for me.
Hope this will help somebody.
If you want to embed documents you can use embedded_many feature of mongoid, which handles such relations. It allows you to define index on embedded documents as well
http://mongoid.org/en/mongoid/docs/relations.html#embeds_many
Mongoid points out, that 2D indexes should be applied to arrays:
http://mongoid.org/en/mongoid/docs/indexing.html
In your case models may look like this:
class User
include Mongoid::Document
field :name, type: String
embeds_many :locations
index({ "locations.loc" => "2d" })
accepts_nested_attributes_for :locations # see http://mongoid.org/en/mongoid/docs/nested_attributes.html#common
end
class Location
include Mongoid::Document
field :time, type: DateTime # see http://mongoid.org/en/mongoid/docs/documents.html#fields
field :loc, type: Array
embedded_in :user
end
But beware of using update and nested attributes - it allows you only update attributes, but not delete or reject them. It's preferrable to use (association)_attributes= methods instead:
#user = User.new({ name: 'John Doe' })
#user.locations_attributes = {
"0" => {
_id : ObjectID(...),
time : "...." ,
loc : [ 55.5, 42.3 ]
} ,
"1" => {
_id : ObjectID(...),
time : "...",
loc : [ -74 , 44.74 ]
}
}
#user.save!

Mongoid has_and_belongs_to_many inverse_of: :nil unexpected data

I have a collection of Tasks that relate back to themselfs
class Task
include Mongoid::Document
has_and_belongs_to_many :related_tasks , class_name: 'Task', inverse_of: :nil
In the monogo data I am looking for
Parent task
{
"_id" : ObjectId(""),
"related_task_ids" : [
ObjectId(""),
ObjectId("")
],
}
And on the child task (nothing)
The parent tasks looks correct.
But on the child task I get
{
"_id" : ObjectId(""),
"nil_ids" : [
ObjectId("")
],
"related_task_ids" : [ ],
}
Where nil_ids is the parent id.
Why is it storing the nil_id's? and is there any way to stop this?
I want a 1..n relationship i.e a task has many children.
It's not a n..n relationship i.e. Children tasks don't have many parent tasks.
The reason you are seeing a nil_ids key on the child side of the association is that you have specified the :nil Ruby symbol rather than nil. So Mongoid is just interpreting this like any other symbol and creating a nils collection on the Task as the inverse of the related_tasks collection.
Try:
has_and_belongs_to_many :related_tasks , class_name: 'Task', inverse_of: nil
This should leave the related_task_ids in the parent task but not store the nil_ids on the children.

Elasticsearch with Tire on Rails bulk import & indexing issue

I've a rails app with a full-text search based on Elasticsearch and Tire, it is already working on a MongoDB model called Category, but now I want to add a more complex search based on MongoID Embedded 1-n model User which embeds_many :watchlists
Now I have to bulk import and indexing all the field in Watchlist, and I'd like to know :
how can I do that ?
can index just the watchlists children fields, without the user parents fields ?
The Embedded 1-N MongoDB/MongoID model looks like the following :
app/models/user.rb ( the parent ) :
class User
include Mongoid::Document
include Tire::Model::Search
include Tire::Model::Callbacks
index_name 'users'
field :nickname
field ... many others
embeds_many :watchlists
end
app/models/watchlist.rb ( the embedded "many" childrens ) :
class Watchlist
include Mongoid::Document
include Tire::Model::Search
include Tire::Model::Callbacks
index_name 'watchlists'
field :html_url
embedded_in :user
end
Any suggestion on how to accomplish the task ?
UPDATE:
here it is a chunk of the model seen with mongo shell
> user = db.users.findOne({'nickname': 'lgs'})
{
"_id" : ObjectId("4f76a16cf2a6a12f88cbca43"),
"encrypted_password" : "",
"sign_in_count" : 0,
"provider" : "github",
"uid" : "1573",
"name" : "Luca G. Soave",
"email" : "luca.soave#gmail.com",
"nickname" : "lgs",
"watchlists" : [
{
"_id" : ObjectId("4f76997f1d41c81173000002"),
"tags_array" : [ git, peristence ],
"html_url" : "https://github.com/mojombo/grit",
"description" : "Grit gives you object oriented read/write access to Git repositories via Ruby.",
"fork_" : false,
"forks" : 207,
"watchers" : 1258,
"created_at" : ISODate("2007-10-29T14:37:16Z"),
"pushed_at" : ISODate("2012-01-27T01:05:45Z"),
"avatar_url" : "https://secure.gravatar.com/avatar/25c7c18223fb42a4c6ae1c8db6f50f9b?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png"
},
...
...
}
I'd like to index & query any fields owned by the embedded child watchlists doc :
... "tags_array", "html_url", "description", "forks"
but I don't want elasticsearch to include the parent user fields :
... "uid", "name", "email", "nickname"
so that when I query for "git persistence", it will look into each 'watchlists' indexed fields of each 'user' of the original MongoDB.
(sorry for mismatching singular and plurals here, I was just indicating the doc object names)
It really depends on how you want to serialize your data for the search engine, based on how you want to query them. Please update the question and I'll update the answer. (Also, it's better to just remove the ES logs, they are not relevant here.)
I'm not sure how the Rake task works with embedded documents in Mongo, and also why it seems to "hang" at the end. Is your data in the "users" index when you run the task?
Notice that it's quite easy to provide your own indexing code, when the Rake task is not flexible enough. See the Tire::Index#import integration tests.

Modeling many-to-many :through with Mongoid/MongoDB

I'm relatively new to Mongoid/MongoDB and I have a question about how to model a specific many-to-many relationship.
I have a User model and a Project model. Users can belong to many projects, and each project membership includes one role (eg. "administrator", "editor", or "viewer"). If I were using ActiveRecord then I'd set up a many-to-many association between User and Project using has_many :through and then I'd put a field for role in the join table.
What is a good way to model this scenario in MongoDB and how would I declare that model with Mongoid? The example below seems like a good way to model this, but I don't know how to elegantly declare the relational association between User and the embedded ProjectMembership with Mongoid.
Thanks in advance!
db.projects.insert(
{
"name" : "Test project",
"memberships" : [
{
"user_id" : ObjectId("4d730fcfcedc351d67000002"),
"role" : "administrator"
},
{
"role" : "editor",
"user_id" : ObjectId("4d731fe3cedc351fa7000002")
}
]
}
)
db.projects.ensureIndex({"memberships.user_id": 1})
Modeling a good Mongodb schema really depends on how you access your data. In your described case, you will index your memberships.user_id key which seems ok. But your document size will grow as you will add viewers, editors and administrators. Also, your schema will make it difficult to make querys like:
Query projects, where user_id xxx is editor:
Again, you maybe do not need to query projects like this, so your schema looks fine. But if you need to query your projects by user_id AND role, i would recommend you creating a 'project_membership' collection :
db.project_memberships.insert(
{
"project_id" : ObjectId("4d730fcfcedc351d67000032"),
"editors" : [
ObjectId("4d730fcfcedc351d67000002"),
ObjectId("4d730fcfcedc351d67000004")
],
"viewers" : [
ObjectId("4d730fcfcedc351d67000002"),
ObjectId("4d730fcfcedc351d67000004"),
ObjectId("4d730fcfcedc351d67000001"),
ObjectId("4d730fcfcedc351d67000005")
],
"administrator" : [
ObjectId("4d730fcfcedc351d67000011"),
ObjectId("4d730fcfcedc351d67000012")
]
}
)
db.project_memberships.ensureIndex({"editors": 1})
db.project_memberships.ensureIndex({"viewers": 1})
db.project_memberships.ensureIndex({"administrator": 1})
Or even easier... add an index on your project schema:
db.projects.ensureIndex({"memberships.role": 1})

Resources