Rails 3.2 Associations and :include - ruby-on-rails

I have some troubles to make an association between 2 tables.
I have Users who can write Posts
Here is my migration file :
class LinkUsersAndPosts < ActiveRecord::Migration
def change
add_column :posts, :user_id, :integer
add_index :posts, :user_id
end
end
My models :
class Post < ActiveRecord::Base
attr_accessible :content, :title
belongs_to :user
end
class User < ActiveRecord::Base
attr_accessible :email, :login, :password, :password_confirmation, :rights
has_many :posts
end
My controller :
class PostsController < ApplicationController
respond_to :json
def index
#posts = Post.includes(:user).all
respond_with #posts
end
def create
#post = Post.new(params[:post])
#post.user = current_user
if #post.save
render json: #post, status: :created, location: #post
else
render json: #post.errors, status: :unprocessable_entity
end
end
end
The posts is correctly created, the user_id is set all is fine.
The problem is when i want to retrieve the list of posts including user, the list of posts is retrieve, but i havn't any data on the user except his id.
Response :
[
{
"content":"jksdjd",
"created_at":"2013-08-31T09:03:01Z",
"id":11,"title":"kdjs",
"updated_at":"2013-08-31T09:03:01Z",
"user_id":4
},
{
"content":"tez2",
"created_at":"2013-08-31T09:16:45Z",
"id":12,
"title":"test2",
"updated_at":"2013-08-31T09:16:45Z",
"user_id":4
}
]

By default a JSON response won't return any associated models. You need to specify the other models you want returned. So in your case, you can do this:
render json: #post => #post.to_json(:include => :user), status: :created, location: #post
Also see:
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
Rails Object Relationships and JSON Rendering

Related

How to assign a user id to a record in the backend?

I want to make sure that when I create a record in the front end, the user id of the user who created it is automatically assigned to this record. What should I do in the backend if I want the id of the authorized user to be automatically assigned to the record when creating the record? For authorization I use gem 'devise_token_auth'.
notebooks_controller.rb:
class NotebooksController < ApplicationController
before_action :set_notebook, only: [:show, :update, :destroy]
def index
#notebooks = Notebook.all
render json: #notebooks
end
def show
render json: #notebook
end
def create
#notebook = Notebook.new(notebooks_params)
if #notebook.save
render json: #notebook, status: :created, location: #notebook
else
render json: #notebook.errors, status: :unprocessable_entity
end
end
def update
if #notebook.update(notebooks_params)
render json: #notebook
else
render json: #notebook.errors, status: :unprocessable_entity
end
end
def destroy
#spr_contract_solution.destroy
end
private
def set_notebook
#notebook = Notebook.find(params[:id])
end
def notebooks_params
params.require(:notebook).permit!
end
end
notebook.rb:
class Notebook < ApplicationRecord
belongs_to :user
end
...._create_notebooks.rb
class CreateNotebooks < ActiveRecord::Migration[5.1]
def change
create_table :notebooks do |t|
t.string :title
t.text :description
t.boolean :is_active
t.references :user, foreign_key: true
t.timestamps
end
end
end
First, #Subash is right, you need to pass the list of parameters to the permit method in notebook_params (note that maybe you would want to use permit instead of permit!), for example:
params.require(:notebook).permit :name, :text, :any_other_attribute_you_are_saving
Then, to answer your question, you could do something like this in the create action:
#notebook = Notebook.new(notebooks_params)
#notebook.user = current_user #Assuming you have this method available
Assuming you have has_many :notebooks in your user model, this is a popular idiom for doing what you want:
#notebook = current_user.notebooks.build(notebook_params)
add
#notebook.user = current_user
after
#notebook = Notebook.new(notebooks_params)

HABTM relationship with rails API does not work

I am new in Ruby on Rails. I am making a Rails API using Rails 5.1, active record serializer, doorkeeper and devise gem.
I have an Order table and it has many products. The relation between order and product is many-to-many.
Order model:
class Order < ApplicationRecord
validates_presence_of :brute, :net
has_and_belongs_to_many :products
end
Product model:
class Product < ApplicationRecord
belongs_to :category
validates_presence_of :name, :price
validates_uniqueness_of :name
has_and_belongs_to_many :orders
end
I have a join table named orders_products.
Order serializer:
class OrderSerializer < ActiveModel::Serializer
attributes :id, :discount, :brute, :net, :payed, :payed_at, :products
def products
object.products.map do |product|
ProductSerializer.new(product, scope: scope, root: false, event: object)
end
end
end
Product serializer:
class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :price, :description
has_one :category
end
Order controller:
module Api
class OrdersController < ApiController
before_action :set_order, only: [:show, :update, :destroy]
# GET /api/orders
def index
#orders = Order.all
render json: #orders
end
# GET /api/orders/1
def show
render json: #order
end
# POST /api/orders
def create
#order = Order.new(order_params)
if #order.save
render json: #order, status: :created, location: api_order_url(#order)
else
render json: #order.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /api/orders/1
def update
if #order.present?
if #order.update(order_params)
render json: #order
else
render json: #order.errors, status: :unprocessable_entity
end
end
end
# DELETE /api/orders/1
def destroy
#order.destroy if #order.present?
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
rescue ActiveRecord::RecordNotFound
Rails.logger.error{ 'Order record is not found' }
nil
end
# Only allow a trusted parameter "white list" through.
def order_params
params.require(:order).permit(:discount, :brute, :net, :payed, :payed_at, product_ids: [])
end
end
end
When I post some order json data from API generator app like Postman/Insomnia, Order is being saved in orders table but no data saved in orders_products join table.
My request(POST http://localhost:3000/api/orders) of order json:
{
"discount": 110,
"brute": 100,
"net": 200,
"payed": null,
"payed_at": null,
"product_ids": [3]
}
I try to find the solution but I failed.
Finally I have solved in your problem.Just add an attribute in your model.
Order Model:
class Order < ApplicationRecord
attribute :product_ids
validates_presence_of :brute, :net
has_and_belongs_to_many :products
end
Order Serializer:
class OrderSerializer < ActiveModel::Serializer
attributes :id, :discount, :brute, :net, :payed, :payed_at
has_many :products
end
And create method in your order api:
# POST /api/orders
def create
#order = Order.new(order_params)
if #order.save
# Find products
#products = Product.where(id: order_params[:product_ids])
# Create join table records
#products.each { |product| product.orders << #order }
render json: #order, status: :created, location: api_order_url(#order)
else
render json: #order.errors, status: :unprocessable_entity
end
end
I have tested in locally and it works! Happy Programming :)
As far as I know, Rails doesn't automatically handle creating the join records when given a list of ids. Therefore when you're calling #order = Order.new(order_params) and expecting it to know how to handle product_ids: [3], it's just ignoring it.
If you modify your create endpoint with the below, you should see the join records being created.
# POST /api/orders
def create
#order = Order.new(order_params)
if #order.save
# Find products
#products = Product.where(id: order_params[:product_ids])
# Create join table records
#products.each { |product| product.orders << order }
render json: #order, status: :created, location: api_order_url(#order)
else
render json: #order.errors, status: :unprocessable_entity
end
end
This is just one possible solution that doesn't do any error checking. Depending how secure and robust your application needs to be you may need to create a service that wraps this and handles validating that products are found before creating the order and associating the records.
EDIT: OrderSerializer
Once you've verified that the join table records are being created properly. Check that your serializers are working, they have great documentation. I believe you can swap out your current products method in the OrderSerializer with this:
class OrderSerializer < ActiveModel::Serializer
attributes :id, :discount, :brute, :net, :payed, :payed_at, :products
def products
object.products.map do |product|
ProductSerializer.new(product).serializable_hash
end
end
end

rails : association tabel (has_and_belongs_to_many) not save any record

i use rails 5 , simple form. in my app there is a Category model and there is a OnlineProduct model. i dont know why when i want to add some categories to my OnlineProduct association table remain empty and don't change.
Category model:
class Category < ApplicationRecord
has_ancestry
has_and_belongs_to_many :internet_products
end
InternetProduct model:
class InternetProduct < ApplicationRecord
belongs_to :user
belongs_to :business
has_and_belongs_to_many :categories
end
InternetProduct controller:
def new
#internet_product = InternetProduct.new
end
def create
#internet_product = InternetProduct.new(internet_product_params)
respond_to do |format|
if #internet_product.save
format.html { redirect_to #internet_product, notice: 'Internet product was successfully created.' }
format.json { render :show, status: :created, location: #internet_product }
else
format.html { render :new }
format.json { render json: #internet_product.errors, status: :unprocessable_entity }
end
end
end
private:
def internet_product_params
params.require(:internet_product).permit(:name, :description, :mainpic, :terms_of_use,
:real_price, :price_discount, :percent_discount,
:start_date, :expire_date, :couponـlimitation, :slung,
:title, :meta_data, :meta_keyword, :enability, :status,
:like, :free_delivery, :garanty, :waranty, :money_back,
:user_id, :business_id,
categoriesـattributes: [:id, :title])
end
and in the view only the part of who relate to categories :
<%= f.association :categories %>
all the categories list in view (form) but when i select some of them not save in database. in rails console i do this
p = InternetProduct.find(5)
p.categories = Category.find(1,2,3)
this save to database without any problem, what should i do ?
tanks for reading this
I found solution to solve this. when we use has_and_belong_to_many or any other relation , if you want to use collection select in simple_form , in the model also should be add this command for nesting form
accepts_nested_attributes_for :categories
also in the controller in related method for example in the new we should
def new
#internet_product = InternetProduct.new
#internet_product.categories.build
end

ForbiddenAttributesError error when building from a has_many relationship

New updates
Moved params permit responsability from model to controller and used comment_attributes instead of comments as #vinodadhikary pointed me
Using better_errors REPL, I traced the problem down to sanitize_for_mass_assignment method. When doing attributes.permitted? it returns false. But doing attributes.permit(:article_id, :name, :email, :body) returns me exactly que entry parameters!:
>> attributes
=> {"name"=>"Commenter", "email"=>"commenter#mail.com", "body"=>"Here is the comment >> body!! :D"}
>> attributes.permit(:article_id, :name, :email, :body)
=> {"name"=>"Commenter", "email"=>"commenter#mail.com", "body"=>"Here is the comment body!! :D"}
>> attributes.permitted?
=> false
Context and code
Trying to get in touch with Rails 4, I encountered a problem with (I think) strong parameters use.
I have an Article class which can have many Comments. When creating a new comment doing:
#comment = #article.comments.build(params[:comment])
I get the following error (pointing this line):
ActiveModel::ForbiddenAttributesError at /articles/1/comments
The models are the following:
class Article < ActiveRecord::Base
validates_presence_of :title, :content
validates_uniqueness_of :title
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
Comments:
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id, :author, :body, :content
end
Article controller have this in the private section:
def article_params
params.require(:article).permit(:title, :content, comments_attributes: [:article_id, :name, :email, :body])
end
Comments controller code is:
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment]) # <--- It fails here
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
The methods article_params and comment_params that you have in the models belong in their respective controllers not in models. The idea is to filter the parameters passed to the model in the controller rather than in the model. Take a read on http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html, on how to allow attributes for nested attributes.
You models should be as follows:
# Articles.rb
class Article < ActiveRecord::Base
validates_presence_of :title, :content
validates_uniqueness_of :title
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
# Comment.rb
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id, :author, :body, :content
end
Then move the strong parameters to Articles Controller as follows:
#ArticlesController.rb
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
def article_params
params.require(:article).permit(:title, :content, comments_attributes: [:article_id, :author, :email, :body, :content])
end
permit params method name should be same as model/controller
e.g if model name is "recent_post" then permit method name should be
def recent_post_params
..............
end

Ruby On Rails Pagination and delete :through association

I am running into a strange situation, considering the following models:
class Collection < ActiveRecord::Base
attr_accessible :name, :season, :year
has_many :collection_items_assocs
has_many :items, :through => :collection_items_assocs
end
class Item < ActiveRecord::Base
attr_accessible :name, :reference, :item_type_id
has_many :pictures
has_one :item_type
end
class CollectionItemsAssoc < ActiveRecord::Base
attr_accessible :collection_id, :item_id
belongs_to :item
belongs_to :collection
end
I can successfully retrieve Items associated to a Collection with the following code:
# GET /collections/1
# GET /collections/1.json
def show
#collection = Collection.find(params[:id])
#collection.items = Collection.find(params[:id]).items
respond_to do |format|
format.json { render json: #collection.to_json(:include => {:items => #collection}) }
end
end
But when I try to include pagination (for items) like that
# GET /collections/1
# GET /collections/1.json
def show
#collection = Collection.find(params[:id])
**#collection.items = Collection.find(params[:id]).items.paginate(:page => params[:page],:per_page =>1)**
respond_to do |format|
format.json { render json: #collection.to_json(:include => {:items => #collection}) }
end
end
It works for the following call
/retailapp/collections/1?format=json&**page=1**
Then if I call
/retailapp/collections/1?format=json&**page=2**
the records in the association table CollectionItemsAssoc are deleted
I really don't get it
Thanks for your help
The problem is the code to fetch the items
#collection.items = Collection.find(params[:id]).items
it assigned the fetched items to current collection object.
you need to change the response to support the pagination on associate objects
  def show
   #collection = Collection.find(params[:id])
respond_to do |format|
format.json {
json_hash = #collection.as_json
json_hash[:items] = #collection.items.paginate(:page => params[:page],:per_page =>1).as_json
render json: json_hash.to_json
}
end
Additionally you can overwrite to_json method inside Collection model.

Resources