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.
Related
In my Rails (api only) learning project, I have 2 models, Group and Album, that have a one-to-many relationship. When I try to save the group with the nested (already existing) albums, I get the following error, ActiveRecord::RecordNotFound (Couldn't find Album with ID=108 for Group with ID=). I'm using the jsonapi-serializer gem. Below is my current set up. Any help is appreciated.
Models
class Group < ApplicationRecord
has_many :albums
accepts_nested_attributes_for :albums
end
class Album < ApplicationRecord
belongs_to :group
end
GroupsController#create
def create
group = Group.new(group_params)
if group.save
render json: GroupSerializer.new(group).serializable_hash
else
render json: { error: group.errors.messages }, status: 422
end
end
GroupsController#group_params
def group_params
params.require(:group)
.permit(:name, :notes, albums_attributes: [:id, :group_id])
end
Serializers
class GroupSerializer
include JSONAPI::Serializer
attributes :name, :notes
has_many :albums
end
class AlbumSerializer
include JSONAPI::Serializer
attributes :title, :group_id, :release_date, :release_date_accuracy, :notes
belongs_to :group
end
Example JSON payload
{
"group": {
"name": "Pink Floyd",
"notes": "",
"albums_attributes": [
{ "id": "108" }, { "id": "109" }
]
}
}
If the albums already exist, then accepts_nested_attributes is not necessary.
You could save them like this:
Group.new(name: group_params[:name], notes: group_params[:notes], album_ids: group_params[:album_ids])
You will want to extract the album_ids as an array when passing it here.
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}] }
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
I have a model that accepts nested attributes for another model, like so:
class House < ActiveRecord::Base
has_one :owner
attr_accessible :owner_attributes
accepts_nested_attributes_for :owner
end
I want to be able to call :owner_attributes just :owner, and be able to POST json that looks like this:
{
"house": {
"address": "123 Main St",
"owner": {
"name": "Jim",
"age": 9000
}
}
This seems like it goes against rails conventions, but I was just trying to figure out if its possible or not.
Add this to your controller
private
def house_params
params.require(:house).permit(:house_field_a, :house_field_b, owner_attributes: [:name, :etc])
end
This will convert the owner parameter into owner_attributes allowing you to POST a nested owner object.
Then in the controller action you can just use house_params instead of params[:house]
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!