繁体   English   中英

通过关联防止 has_many 中的重复项?

[英]Preventing duplicates in a has_many through association?

我有两个表/模型( UsersConcerts )有一个Posts的连接表 model 。 这是针对“ticketmaster”风格的市场,因此User可以在任何给定的ConcertPost ,并且Concert也可以通过该User发布的Post获得关于给定User的信息。

问题是我的 /concerts 上有用户重复,我的用户上有 concerts 重复; 我也不知道为什么。 下面是 /concerts 和 /users 的 JSON output。

/音乐会在这里:

{
  "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": "eusebia_larson@wilderman.co"
      },
     {
      "id": 257,
      "username": "onlineguy1",
      "email": "eusebia_larson@wilderman.co"
      },
     {
      "id": 273,
      "username": "L0V3MUSIC",
      "email": "lulu_lemke@johns.name"
      }
   ]
},

对于 /users,它看起来像这样,您可以看到更多问题:

{
   "id": 257,
   "username": "onlineguy1",
   "email": "eusebia_larson@wilderman.co",
   "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
      },
    ]
},

下面是我的帖子 model,我的用户 model,我的演唱会 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

如果有人朝正确的方向迈出了一步,我会很乐意接受,因为我无法弄清楚。 一直在研究文档,但我在某个地方让自己兴奋起来

编辑:包括我的控制器、序列化器和路由。

此外,下面的控制器,从 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

这是给用户的:

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

这是音乐会:

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

这是序列化程序:

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

这是 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

显示posts: []的事实我假设是为了简洁起见,因为如果没有posts元素,就没有users可以参加Concert

您的问题是您通过Post加入UserConcert ,并且可以合理地假设User可能不止一次发布有关Concert的信息,不是吗?

鉴于您当前的关系以及您正在使用ActiveModel::Serializer的事实,您将不得不覆盖 Association 方法以仅返回不同的User / Concerts

例如:

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

注意:我不确定这不会像它应该出现的那样以循环依赖结束(我不将此库用于 API)

我在定义模型时通过使用distinct属性设法解决了这个问题。

class Concert < ApplicationRecord
  belongs_to :artist
  
  has_many :posts
  has_many :users, -> { distinct }, through: :posts

end

通过使用 --> {distinct},我只在 JSON 中得到不同的(即没有重复的)对象。这是否是最佳方式,我不能说,但它确实解决了我原来的问题所以我我自己回答这个问题。 如果你被困在同一条船上,你可以在这里阅读更多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM