How can I store nested json data in Rails? - ruby-on-rails

I have 3 models:
class Depot < ActiveRecord::Base
has_many :car_amounts
has_many :cars, :through => :car_amounts
end
class CarAmount < ActiveRecord::Base
belongs_to :depot
belongs_to :car
end
class Car < ActiveRecord::Base
has_many :car_amounts
has_many :depots, :through => :car_amounts
end
What is the best way to store json paramers, which contains depot, amounts and cars data. Something like this:
{
"Depots": {
"title": "Dealer1"
},
"Amounts": [
{
"amount": "1"
"car": [
{
"Type": "supercar1"
}
]
},
{
"amount": "5"
"car": [
{
"Type": "supercar2"
}
]
},
]
}

I am a little unclear what your question is, but I think you might be looking for accepts_nested_attributes_for. The documentation can be found here http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html. From the documentation:
Nested attributes allow you to save attributes on associated records through the parent.
As an example, you could add accepts_nested_attributes_for :cars in you Depots model. While it wouldn't quite work with the JSON you've shown above, it works well with the general idea.
If this isn't quite your question then please comment and I will clarify.

If this data needs to be in you your database then I would 'seed' the data and import it into your database. You can create a file db/seed.rb and lets say I want to import students into my database. I have a student model already created/migrated. In my seed.rb I would do something like...
students = ActiveSupport::JSON.decode(File.read('db/students.json'))
students.each do |a|
Student.find_or_create_by_id_number(:id_number => a['id_number'], :username => a['username'], :first_name => a['first_name'], :last_name => a['last_name'])
end
...and my students.json looks like...
[
{"id_number": "00xx280","username": "jesxxne","first_name": "Jessie","last_name": "Lane","email": "jesxxane#xx.edu","phone_number": "602-321-6693","is_active": true ,"last_four": "1944" },
{"id_number": "11xx2","username": "jamxxacqua","first_name": "James","last_name": "Bevixxua","email": "jamesbxxacqua#xx.edu","phone_number": "828-400-5393","is_active": true ,"last_four": "5422" }
]
Now you can run rake db:seed to import this data. Good luck!

Related

Rails API: send request with nested resources through postman

I've created a rails api only application. The association between the models are as follows:
class Book < ApplicationRecord
has_and_belongs_to_many :authors
accepts_nested_attributes_for :authors, allow_destroy: true
end
class Author < ApplicationRecord
has_and_belongs_to_many :books
end
Now, when I'm trying to create a new Book with parameters posted from postman,
{
"book": {
"name": "Angels and Demons",
"isbn": "12323012123213",
"authors_attributes": [{"id": 1}, {"id": 2}]
}
}
it gives me the following error: though Author with id 1 exists in DB.
"message": "Couldn't find Author with ID=1 for Book with ID="
If I changed my form parameters like the following:
{
"book": {
"name": "Angels and Demons",
"isbn": "12323012123213",
"authors_attributes": [{"0": {"id": 1}}, {"1": {"id": 2}}]
}
}
It gives me validation error from the Author model.
Controller strong parameters:
def book_params
params.require(:book).permit(:name, :isbn, authors_attributes: [:id, :_destroy])
end
Any idea what I'm doing wrong?
If you want to associate a book with existing authors you don't need nested attributes- just pass an array of ids as book_ids:
{
"book": {
"name": "Angels and Demons",
"isbn": "12323012123213",
"author_ids": [1,2]
}
}
ActiveRecord will create *association_name*_ids getters and setters for all has_many and HABTM associations.
Use nested attributes only if the associated record needs to be created/updated on the fly in the same request. For an API app, I would avoid nested attributes since you end up bending the Single Responsibility Principle.

Rails 5: includes for polymorphic with where conditions

I need to query my models and produce a record similar to this:
[{
"subscriber": {
"email": "user#example.com",
"subscriptions": [{
"confirmed": true,
"subscriptionable": {
"name": "Place XYZ",
"comments": [{
"author": "John",
"body": "Hello."
}]
}
}]
}
}]
My models look like this:
class Subscriber < ApplicationRecord
has_many :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :subscriptionable, polymorphic: true
belongs_to :subscriber
end
class Place < ApplicationRecord
has_many :subscriptions, as: :subscriptionable
has_many :comments, as: :commentable
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
So far I'm able to produce the record I want by running this query:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
The problem is I that, with the query above, I can't specify any where conditions. For example, this will fail:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
.where(subscriptions: { confirmed: true })
> ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :subscriptionable
And the other issue is that I can't just get certain attributes, for example:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
.pluck("subscribers.email")
> ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :subscriptionable
Edit
Maybe this will help clarify: what I would like to achieve is something in the lines of this SQL query:
SELECT subscriptions.name as sub_name, subscriptions.email as sub_email,
places.name as place_name, comments.author as com_author, comments.body as com_body, subscribers.token
FROM subscriptions
JOIN subscribers
ON subscriptions.subscriber_id = subscribers.id
JOIN places
ON subscriptionable_id = places.id
JOIN comments
ON places.id = commentable_id
WHERE subscriptions.confirmed
AND subscriptionable_type = 'Place'
AND commentable_type = 'Place'
AND comments.status = 1
AND comments.updated_at >= '#{1.week.ago}'

Include properties for ActiveModelSerializer only if called within has many

I have a rails app with the following models.
class Project
has_many :project_clips
has_many :clips, through: :project_clips
end
class Clip
has_many :project_clips
has_many :projects, through: :project_clips.
end
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :clips
end
class ClipSerializer < ActiveModel::Serializer
attributes :id, :name
end
I was wondering if it's possible to display the values of the associated project_clip, if the clip has been called within the context of project.
Let's say the ProjectClip model, has a field called priority. I want the results to show up like this.
{ projects: { "id": 1, "name": "ipsum", "clips": [{ "id": 1, "name": "lorem", "priority": "high"}] } }
I don't want the values of project_clips to be included, just a few properties when returning the data for projects.
If I'm getting your question right, you can do something like:
res = project.clips.collect{ |clip| [clip, clip.project_clips] }
or if you want to return hashes and not objects, you can do:
res = project.clips.collect{ |clip| [clip.attributes, clip.project_clips.collect{|pc| pc.attributes}] }

Serialize a summary of a has_many relationship

How can I include a summary of the associated objects rather than the objects itself. For example, if a client has_many projects I could do this:
class ClientSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :projects
end
But this will return all of the associated projects. I would much rather bring back just a count of the projects, the url to download the full list of projects, the last time a project was updated, etc.
What is the best way to include a summary of the associated objects?
Ideally, for example the resulting JSON would look like this:
{
"id": 10,
"name": "My Client",
"projects": {
"count": 5,
"updated_at": "2014-09-09T13:36:20.000-04:00",
"url": "https://my.baseurl.com/clients/10/projects"
}
I'm not sure if this is the best way to do it, but I got this to work:
class ClientSerializer < ActiveModel::Serializer
attributes :id, :name, :archive, :updated_at, :projects
def projects
collection = object.projects.to_a
{ count: collection.length,
updated_at: collection.map(&:updated_at).max,
url: projects_url }
end
end
You could create an instance method:
class ClientSerializer < ActiveModel::Serializer
has_many :projects
def project_count
projects.size
end
end

Confusion over generating custom JSON response in Rails

Model description:
User, Widget, Purchase
User
has_many :purchases
has_many :widgets, :through => :purchases
Widget
has_many :purchases
has_many :users, :through => :purchases
Purchase
belongs_to :user
belongs_to :widget
Hope that makes sense and is correct for the below.
Right, in my controller, I have current_user.
I wish to render a nested JSON response for use eventually with jquery templates.
Like this would be ideal:
{
"purchases": [
{
"quantity": 200,
"price": "1.0",
"widget": {
"name": "Fantastic Widget",
"size": "Large"
}
},
{
"quantity": 300,
"price": "3.0",
"widget": {
"name": "Awesome Widget",
"size": "Medium"
}
}
]
}
This:
render :json => current_user.to_json(:include => [:purchases, :widgets])
Will render some current_user details (not really relevant) and then purchases and widgets at the same level.
This works (outputs some current user details but thats not my main gripe at the moment):
render :json => current_user.to_json({:include => :purchases })
But obviously only outputs purchase data. I cannot get a nested include to work (should it work?) even after looking at this sample:
konata.to_json(:include => { :posts => {
:include => { :comments => {
:only => :body } },
:only => :title } })
From here and this existing stackoverflow question.
So I've just got myself thoroughly confused. Help appreciated.
I would look into utilizing the as_json method in your models to get the desired output. Adding the following to your models should get you going in the right direction.
#users model
def as_json(options={})
{:purchases => self.purchases}
end
#purchases model
def as_json(options={})
{:quantity: self.quantity,
:price: self.price,
:widget: self.widget}
end
#widgets model
def as_json(options={})
{:name:self.name,
:size: self.size}
end
Once you've added these you'd simply user to_json on your user instance and it should output correctly.
RABL lets you craft JSON views similar to the HTML ERB views.
This blog post, "If you’re using to_json, you’re doing it wrong", explains a bit of background and the shortcomings of using to_json.

Resources