Create entry for nested model with has_many and through - ruby-on-rails

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

Related

Rails 5 create multiple child objects using parent object by the use of nested attributes

So, I'm fairly new to Rails and I'm stuck due to my model's complexity.
I have a Developer model, a Township model and a Project model and their contents are as follows:-
Developer.rb
Class Developer < ApplicationRecord
has_many :townships,
has_many :projects, through: :townships
accepts_nested_attributes_for :township
end
Township.rb
Class Township < ApplicationRecord
belongs_to :developer
has_many :projects
accepts_nested_attributes_for :project
end
Project.rb
Class Project < ApplicationRecord
belongs_to :township
end
I want to create projects such as follows:-
project = Developer.create(
{
name: 'Lodha',
township_attributes: [
{
name: 'Palava',
project_attributes: [
{
name: 'Central Park'
},
{
name: 'Golden Tomorrow'
}
]}
]})
Any ideas as to how can I accomplish this? I also need to understand the strong params whitelisting required in the DeveloperController.
I don't know of a way for you to create it in one line (plus it would be less readable) , but you can do this with rails using similar code to below:
def create
developer = Developer.new(name: 'Loha')
township = developer.townships.build({ name: 'Palava' })
# for this part I don't know what your form looks like or what the
# params look like but the idea here is to loop through the project params like so
params[:projects].each do |key, value|
township.projects.build(name: value)
end
if developer.save
redirect_to #or do something else
end
end
Saving the developer will save all of the other things with the correct foreign keys assuming you have them set up correctly. Just pay attention to the format of your params to make sure you're looping through it correctly.

Nested routes and CRUD operations with additional values for assciation in Rails

I created one has_many through relationship in rails api. I also used nested routes.
My models like below;
class Author < ActiveRecord::Base
has_many :comments
has_many :posts, :through => :comments
end
class Post < ActiveRecord::Base
has_many :comments
has_many :authors, :through => :comments
end
class Comment < ActiveRecord::Base
belongs_to :author
belongs_to :post
end
my routes like below;
Rails.application.routes.draw do
namespace :api, defaults: { :format => 'json' } do
resources :posts do
resources :comments
end
resources :authors
end
end
So here my aims are
Comments are nested route so that i can create and display comments from post
Here not any post author. The author is meant for comment owner
I implemented the concepts and working almost all. But i am facing the following 2 problems with associations
How to add additional fields for associated table when parent create. Here my requirement is when a post is created, i need to insert one default entry for comment. My post controller implementation for create is like below
def create
params = create_params.merge(:record_status => 1)
#post = Post.new(params)
#post.authors << Author.find(1)
if #post.save
render :show, status: :created
end
end
def show
#post = Post.find(params[:id])
end
private
def create_params
params.permit(:name, :description, :author_id )
end
Here i am passing author_id in the request json. This needs to be added as author id in the comments table. Now i just hard coded as 1. I used '<<' for association entry. This is working but i also need to include two more fields which are :comments and :record_status. Again :comments is from the request itself.
Note: This is not rails mvc application. This is rails api and input as json.
When i display comments using nested routes i need to show author and also comments from comments table. My comments controller method is ;
class Api::CommentsController < ApplicationController
before_filter :fetch_post
def index
#authors = #post.authors.where(:record_status => 1, comments: { record_status: 1 })
end
private
def fetch_post
#post = Post.find(params[:post_id])
end
end
Here i got authors but not correct comments in the join table 'comments'
Please help me to solve these issues
For the first problem, you want to configure the posts_controller to accept the nested attributes for comments. Add this line at the beginning of your controller:
accepts_nested_attributes_for :comments
Check the documentation for further details on this method.
Then, you need to modify the parameters that the controller will allow:
def create_params
params.permit(:name, :description, :author_id, comments_attributes: [ :author_id, :comments, :record_status ] )
end
Modify the attributes listed in the comments_attributes array to match those of your Comment model.
I'm not sure what you're trying to get in the second problem, but maybe you just need to query a little differently:
#comments = Comment.where(post_id: #post.id).includes(:author)
The above would return a list of comments including the comment author.

Rails many to many model, adding records

Hello im trying to add some records to my database with this model
class Colleagueship < ActiveRecord::Base
belongs_to :employee
belongs_to :colleague, :class_name => 'Employee'
end
class Employee < ActiveRecord::Base
has_many :colleagueships
has_many :colleagues, :through => :colleagueships
# ...
end
but i have no idea in how to start a new form to create new records
im thinking to try something like
def new
employee = ## gotta get the id here in the form
#colleagueship = employee.colleagueships.build(:colleague_id => params[:colleague_id])
#colleagueship.save
end
what do you think? how do i achieve this with a post http method? do i have to save the employee variable with the request and add the employee_id there?
In the controller
def new
end
def create
# inspect submitted params here
puts params
if colleagueship.save
# etc etc
else
# error
end
end
private
def employee
#employee = Employee.find_by(params[:employee_id])
end
def colleagueship
#colleagueship = employee.colleageships.build
end
helper_method :employee, :colleagueship
Your routes should be nested to provide the key you'll use to find the employee.
resources :employees do
# this will generate /employees/:employee_id/colleagues/:id
resources :colleagueships
end
In your view, you will probably use the form_tag helper, as it's easier to customize forms with whatever fields you want, especially if you're avoiding accepts_nested_attributes which you should. You can also include a hidden_field_tag with employee_id if you aren't nested your routes.
= form_tag new_employee_colleague_path do
= text_field_tag 'colleageship[name]', placeholder: 'something...'
Something along these lines should work. Make sure to inspect the params hash to see that the values are formatted correctly.

How to call instance method of comment class through Article Model ?

class Comment < ActiveRecord::Base
belongs_to :article
end
class Article < ActiveRecord::Base
has_many :comments do
def posted_comments
#user_comment is just an attribute of comment.
collect(&:user_comment)
end
end
end
to fetch the posted comments :
Article.first.comments.posted_comments
=> ["Nice article posted", "comment 2 added", "Good article"]
Above one is fetching correct results, but I want to have a more compact version.
Something like this:
Article.first.posted_comments
#this should list the collection of comments on the article.
Can we do something like this with Rails ActiveRecord ?
For simply solution, you can define method called posted_comments that calls nested association as the following:
def posted_commments
self.comments.posted_comments
end
Or, Try the following code:
has_many :posted_comments, -> { select("user_comment") }, class_name: "Comment"

How can I associate an existing nested resource on create?

I have this model:
class Product < ActiveRecord::Base
has_many :product_images, dependent: :destroy, autosave: true
accepts_nested_attributes_for :product_images, allow_destroy: true
end
I create a bunch of ProductImage models via ajax before saving a new Product. The ajax creates form inputs for the ProductImage :id, :featured and :_destroy attributes on product_images_attributes and I correctly see these params in my log:
"product" => {"name" => "Test", "product_images_attributes"=>{"0"=>{"id"=>"112", "featured" => "true", "_destroy"=>""}}}
In my controller I'm doing this in #create:
#product = Product.new(params.require(:product).permit!)
#product.save
When passing in those params I get this error when I try to assign the params:
ActiveRecord::RecordNotFound (Couldn't find ProductImage with ID=112 for Product with ID=)
The database shows that ProductImage with ID=112 exists with product_id=null as expected.
It all works when updating an existing Product of course.
How do I associate existing ProductImages with a new record on create using standard rails methods?
nested_attributes is not the way I recommend. It's very contreniant. I suggest to use form objects.
You can do something like this :
class ProductCreation
include ActiveModel::Model
attr_accessor :product_name, :product_image_ids
def save
images = product_image_ids.map {|id| Image.find(id) }
product = Product.new(name: product_name)
product.images = images
product.save
end
end
And use ProductCreation in your controllers and your views.

Resources