I've got two tables/models (Users
and Concerts
) that has a join-table model of Posts
. This is for a 'ticketmaster' style marketplace so a User
can make a Post
on any given Concert
, and a Concert
can also have info on a given User
through the Post
that this User
made.
The problem is that I have user duplicates on my /concerts and concert duplicates on my users; I'm not sure why either. Below are the JSON output of /concerts and /users.
/concerts is here:
{
"id": 45,
"date": "2023-01-19T00:00:00.000Z",
"location": "Brooklyn Steel",
"image": "https://i.imgur.com/SmFrzTC.jpg",
"artist_id": 33,
"artist": {
"id": 33,
"name": "Adele",
"image": "https://i.imgur.com/zmGbfKS.jpg",
"genre": "Pop"
},
"posts": [],
"users": [
{
"id": 257,
"username": "onlineguy1",
"email": "[email protected]"
},
{
"id": 257,
"username": "onlineguy1",
"email": "[email protected]"
},
{
"id": 273,
"username": "L0V3MUSIC",
"email": "[email protected]"
}
]
},
For /users, it looks like this and you can see the issue more:
{
"id": 257,
"username": "onlineguy1",
"email": "[email protected]",
"posts": [],
"concerts": [
{
"id": 45,
"date": "2023-01-19T00:00:00.000Z",
"location": "Brooklyn Steel",
"image": "https://i.imgur.com/SmFrzTC.jpg",
"artist_id": 33
},
{
"id": 45,
"date": "2023-01-19T00:00:00.000Z",
"location": "Brooklyn Steel",
"image": "https://i.imgur.com/SmFrzTC.jpg",
"artist_id": 33
},
{
"id": 46,
"date": "2024-05-23T00:00:00.000Z",
"location": "Mao Livehouse",
"image": "https://i.imgur.com/CghhYym.jpg",
"artist_id": 33
},
{
"id": 46,
"date": "2024-05-23T00:00:00.000Z",
"location": "Mao Livehouse",
"image": "https://i.imgur.com/CghhYym.jpg",
"artist_id": 33
},
{
"id": 47,
"date": "2023-04-29T00:00:00.000Z",
"location": "Madison Square Garden",
"image": "https://i.imgur.com/0gd1dD0.jpg",
"artist_id": 33
},
{
"id": 47,
"date": "2023-04-29T00:00:00.000Z",
"location": "Madison Square Garden",
"image": "https://i.imgur.com/0gd1dD0.jpg",
"artist_id": 33
},
]
},
Below are my post model, my user model, my concert model FWIW.
class User < ApplicationRecord
has_secure_password
validates_uniqueness_of :username, presence: true
# validates :username, presence: true, uniqueness: true
validates :password, length: { minimum: 8, maximum: 254}
validates_presence_of :email
validates_format_of :email, with: URI::MailTo::EMAIL_REGEXP
# validates :my_email_attribute, email: true, presence: true
has_many :posts
has_many :concerts, through: :posts
end
class Post < ApplicationRecord
belongs_to :user
belongs_to :concert
validates :body, presence: true
validates :tickets, presence: true, numericality: { greater_than: 0 }
end
class Concert < ApplicationRecord
belongs_to :artist
has_many :posts
has_many :users, through: :posts
end
If anybody's got a step in the right direction, I'll gladly take it because I can't figure it out. Been poring through docs but I've psyched myself out somewhere
EDIT: to include my Controllers, Serializers, and route.
Also, controllers here below, starting with Post:
class PostsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
def index
posts = Post.all
render json: posts
end
def show
post = Post.find_by!(id: params[:id])
render json: post, status: 200
end
def create
post = Post.create!(new_post_params)
render json: post, status: 201
end
# ## made this one to not-render duplicates but still rendered duplicates
# def create
# ## links the proper user to the post
# correct_user = User.find_by!(id: params[:user_id])
# ## links the proper concert to the post
# correct_concert = Concert.find_by!(id: params[:concert_id])
# newPost = Post.create!(
# id: params[:id],
# body: params[:body],
# tickets: params[:tickets],
# for_sale: params[:for_sale],
# concert_id: correct_concert.id,
# user_id: correct_user.id
# )
# render json: newPost, status: 201
# end
def update
post = Post.find_by!(id: params[:id])
if session[:user_id] === post[:user_id]
post.update!(
body: params[:body],
tickets: params[:tickets]
)
render json: post, status: 200
end
end
def destroy
post = Post.find_by!(id: params[:id])
if session[:user_id] === post[:user_id]
post.destroy
head :no_content
end
end
private
def new_post_params
params.require(:concert_id, :user_id, :for_sale, :tickets, :body)
end
def render_unprocessable_entity_response(invalid)
render json: { errors: invalid.record.errors.full_messages }, status: :unprocessable_entity
end
def render_not_found_response(invalid)
render json: { error: invalid.message }, status: :not_found
end
end
And here's for Users:
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
def index
users = User.all
render json: users
end
## get '/me'
def show
user = User.find_by!(id: session[:user_id]) ## changed it to User.find_by! for it to work
render json: user, status: 200
end
def create
user = User.create!(signup_user_params)
session[:user_id] = user.id
render json: user, status: :created
end
# # the original show
# def show
# user = User.find_by(id: session[:user_id])
# if user
# render json: user, status: 200
# else
# render json: user.errors.full_messages, status: :unprocessable_entity
# end
# end
# # the original create
# def create
# user = User.create(signup_user_params)
# if user.valid?
# session[:user_id] = user.id
# render json: user, status: :created
# else
# render json: user.errors.full_messages, status: :unprocessable_entity
# end
# end
# # update a specific user
# def update
# if user.update(user_params)
# render json: user
# else
# render json: user.errors, status: :unprocessable_entity
# end
# end
# # delete a specific user
# def destroy
# user.destroy
# end
private
def signup_user_params
params.permit(:username, :password, :password_confirmation, :email)
end
def render_unprocessable_entity_response(invalid)
render json: { errors: invalid.record.errors.full_messages }, status: :unprocessable_entity
end
def render_not_found_response(invalid)
render json: { error: invalid.message }, status: :not_found
end
end
And here's Concert:
class ConcertsController < ApplicationController
def index
concerts = Concert.all
render json: concerts
end
def show
concert = Concert.find_by!(id: params[:id])
render json: concert, status: 200
end
## finish after the duplicates issue
def create
## find the proper artist, and link the proper artist
end
end
Here's the Serializers:
class ConcertSerializer < ActiveModel::Serializer
attributes :id, :date, :location, :image, :artist_id
belongs_to :artist, serializer: ArtistSerializer
has_many :posts, serializer: PostSerializer
has_many :users, through: :posts, serializer: UserSerializer
end
class PostSerializer < ActiveModel::Serializer
attributes :id, :body, :for_sale, :tickets, :concert_id, :user_id
belongs_to :user, serializer: UserSerializer
belongs_to :concert, serializer: ConcertSerializer
end
class UserSerializer < ActiveModel::Serializer
attributes :id, :username, :email
has_many :posts, serializer: PostSerializer
has_many :concerts, through: :posts, serializer: ConcertSerializer
end
Here's routes.rb:
Rails.application.routes.draw do
#& Defines the root path route ("/")
#& root "articles#index"
##~ FOR THE ARTIST-CONCERTS-VENUES DISPLAYS
#& getting all the artists-concerts-users
get '/artists', to: "artists#index"
get '/artists/:id', to: "artists#show"
get '/concerts', to: "concerts#index"
get "/users", to: "users#index"
##~ FOR THE POSTS GET/CREATION/EDITS/DELETION
get '/posts', to: "posts#index"
post '/new_post', to: "posts#create"
patch '/update_post/:id', to: "posts#update"
delete '/delete_post/:id', to: "posts#destroy"
##~ THE LOGIN/LOGOUT ROUTES
#& to create a new user outright
post "/new_user", to: "users#create"
#& to login our user
post "/login", to: "sessions#create"
#& to keep the user logged in
get "/me", to: "users#show"
#& to log the user out
delete "/logout", to: "sessions#destroy"
##~ SESSION & COOKIES INFO
#& shows session_id and sessions info
get "/show_session", to: "application#show_session"
#& displays cookies
get "/cookies", to: "application#show_cookies"
# Routing logic: fallback requests for React Router.
# Leave this here to help deploy your app later!
get "*path", to: "fallback#index", constraints: ->(req) { !req.xhr? && req.format.html? }
end
CodePudding user response:
The fact that posts: []
is shown I am going to assume is for brevity becuase there can be no users
for a Concert
without posts
having elements.
Your issue is that you join User
and Concert
through Post
, and it is reasonable to assume that a User
may post more than once about a Concert
is it not?
Given your current relationships and the fact that you are using ActiveModel::Serializer
you are going to have to Override the Association method to return only distinct User
/Concerts
.
For Example:
class ConcertSerializer < ActiveModel::Serializer
attributes :id, :date, :location, :image, :artist_id
belongs_to :artist, serializer: ArtistSerializer
has_many :posts, serializer: PostSerializer
has_many :users, through: :posts, serializer: UserSerializer do
object.users.distinct
end
end
Note: I am not sure how this does not end up in a circular dependency as it appears it should (I don't use this library for APIs)
CodePudding user response:
I managed to solve this by using the distinct
property when defining my models.
class Concert < ApplicationRecord
belongs_to :artist
has_many :posts
has_many :users, -> { distinct }, through: :posts
end
By using --> {distinct}, I only got distinct (i.e. no repeats) objects rendered back in the JSON. Whether or not this is the most optimal way, I can't speak on but it definitely solved my original problem so I'm answering this question myself. You can read more here if you're stuck in the same boat.