Rails accepts nested attributes attr accessible naming - ruby-on-rails

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]

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.

Create entry for nested model with has_many and through

I have three models with simple associations.
class User < ActiveRecord::Base
has_many :blogs
end
class Blog < ActiveRecord::Base
# Blog has 'title' column
belongs_to :user
has_many :entries
end
class Entry < ActiveRecord::Base
# Entry has 'article' column
belongs_to :blog
end
I'm making an JSON API to create new Entry. One special requirement is to create Blog if not exists. The JSON input should be like
{ "entry" :
{ "article" : "my first blog entry",
"blog" :
{ "title": "My new blog" }
}
}
If the blog exists add the entry to the blog. I'm implementing 'entries#create' method and what I want to do is something like
def create
#user = Users.find(params[:user_id]) # :user_id is given in URL
# I want to do something like
# #user.entries.create(params[:entry])
# or
# #user.create(params[:entry])
# but this doesn't work.
end
What I want to ask here is if I have to manually parse the JSON first and create blog object and then create entry object. If possible I want to make the models accept such input and works correctly.
Another possible solution is to change the API and make it in blogs controller and accept JSON like
{ "blog" :
{ "title" : "My new blog",
"article_attributes" :
{ "article": "my first blog entry" }
}
}
but because of some reasons, I can't make the API like this. (The JSON's first node must be "entry" not "blog")
What I have tried so far is to add "accepts_nested_attributes_for" in the Entry model.
class Entry < ActiveRecord::Base
# Entry has 'article' column
belongs_to :blog
accepts_nested_attributes_for :blog
end
and then post JSON like
{ "entry" :
{ "article" : "my first blog entry",
"blog_attributes" :
{ "title": "My new blog" }
}
}
then in the controller
#user.entries.create(params[:entry])
It seems that Rails tries to create "blog" entry with this code but fails because the "blog_attributes" doesn't include 'user_id'. I can add user_id to the params in my controller but it looks awkward since I'm writing #user.entries.create which should tell which user I'm working on now.
Is there any good way to make it all work as I wanted? (or am I doing something totally wrong?)
Ok remove the accepts_nested_attributes_for :blog from your entry model
And in your blog model put accepts_nested_attributes_for :entries, reject_if: :all_blank, allow_destroy: true
In your JSON do this:
{ "blog" :
{ "title" : "My new blog",
"entry" :
{ "article": "my first blog entry" }
}
}
In your blogs_controller.rb do this:
def blog_params
params.require(:blog).permit(:id, :title, entries_attributes: [:id, :article, :_destroy])
end
and in your blogs_controller.rb new action:
def new
#blog = Blog.new
#blog.entries.build
end
// in your blogs_controller create action:
def create
#user = Users.find(params[:user_id]) # :user_id is given in URL
#user.blog.create(blog_params)
end

Use alternate association id for ActiveModel::Serializer association

I have an app where there I have normal ActiveRecord ids as well as a unique field (e.g. ident) that's unique on an external, canonical database. A model looks like:
class Parent
has_many :childs, foreign_key: :parent_ident, primary_key: :ident
end
class Child
belongs_to :parent, foreign_key: :parent_ident, primary_key: :ident
end
For various reasons I'd like the consumer of my Rails API to use the canonical ids (e.g. ident) not the ids defined on the app. So I've defined my serializers (using ActiveModel::Serializer):
class ParentSerializer < ActiveModel::Serializer
attributes :id, :ident, :other, :stuff
has_many :children
def id
object.ident
end
end
class ChildSerializer < ActiveModel::Serializer
attributes :id, ident, :parent_ident, :things
def id
object.ident
end
end
the problem is that the JSON generated correctly is using my overridden IDs for the top-level attributes but the IDs in the child_ids field are the local ids not the canonical idents I want to use.
{
parents: [
{
id: 1234, // overridden correctly in AM::S
ident: 1234,
other: 'other',
stuff: 'stuff',
child_ids: [ 1, 2, 3 ], // unfortunately using local ids
}
],
childs: [
{
id: 2345, // doesn't match child_ids array
ident: 2345,
parent_ident: 1234,
things: 'things'
}
]
}
Question: is there a way to make the parent serializer use the ident field of it's association rather than the default id field?
I have tried putting a def child_ids in the ParentSerializer without success.
I am using Rails 4.2 and the 9.3 version of active_model_serializers gem.
You can specify custom serializers for associations as per the docs
has_many :children, serializer: ChildSerializer

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

Resources