【问题标题】:Is it time for a has_many: : through controller? And if so, what does that look like?has_many: : 是时候通过控制器了吗?如果是这样,那是什么样的?
【发布时间】:2019-01-22 17:59:40
【问题描述】:

我正在创建一个应用程序,让用户可以跟踪他们的棋盘游戏。有一个主库,用于将棋盘游戏添加到用户的个人库中。我的代码中的某些内容或多或少使这两个库成为同义词。每次我删除并重新迁移我的数据库时,它都会自动将主库中的所有游戏添加到用户库中。

但这不是我想要解决的问题,它只是沿着相同的路线。当我点击 axios.delete 函数从人员库中删除棋盘游戏时,它也会触发从主库中删除棋盘游戏。由于我目前设置了后端,因此我的用户 has_many board_games 和 board_games has_many users。我认为我的 axios.delete 在通用 board_game 控制器而不是 user_board_games 中使用了我的破坏功能(即使那是我发送它的地方),所以我认为我需要创建一个特定于 user_board_games 的控制器。但是我对 has_many through 的研究只给了我最基本的设置。

这是组件:

import React, { Component } from 'react';
import axios from 'axios'; 
import { connect } from 'react-redux';
import { Button, Card, Container, Dropdown, Grid } from 'semantic-ui-react'

class Games extends Component {

  state = { games:[], user_games: [], showGames: false, sort: "A-Z" }
    const userId = this.props.user.id 
    axios.get('/api/board_games')
      .then(res => {
        console.log(res.data)
        this.setState({games: res.data});
      })
    axios.get(`/api/users/${userId}/board_games`)
      .then(res => {
        console.log(res.data); 
        this.setState({user_games: res.data});
      } )

  }

  toggleGames = () => {
    this.setState({ showGames: !this.state.showGames })
  }

  removeGame = (id) => {
    const userId = this.props.user.id 
    axios.delete(`/api/users/${userId}/board_games/${id}`)
      .then(res => {
        console.log(res);
      })
  }

  addGame = (id) => {
    const userId = this.props.user.id 
    axios.post(`api/users/${userId}/board_games`, { userId, id })
      .then(res => {
        console.log(res);
      })
  }

  dropDownMenu = () => {
    return  ( <Dropdown text='Sort'>
              <Dropdown.Menu>
                <Dropdown.Item text='A-Z' onClick={() => this.setState({sort: "A-Z"}) }/>
                <Dropdown.Item text='Z-A' onClick={() => this.setState({sort: "Z-A"})} />
                <Dropdown.Item text='Time Needed' onClick={() =>this.setState({sort: "Time Needed"})}  />
              </Dropdown.Menu>
            </Dropdown>
    )
  }

  userLibrary = () => {
    const {user_games, sort} = this.state 
    switch(sort) {
      case 'A-Z':
        user_games.sort(function(game1, game2){
          if(game1.title < game2.title) {return -1; }
          if(game1.title > game2.title) {return 1; }
          return 0; 
        }); 
        break; 
      case 'Z-A':
      user_games.sort(function(game1, game2){
        if(game1.title > game2.title) {return -1; }
        if(game1.title < game2.title) {return 1; }
        return 0; 
      }); 
        break; 
      case 'Time Needed': 
        user_games.sort(function(game1,game2){
          return game1.time_needed-game2.time_needed 
        }) 
        break; 
      default: 
      user_games.sort(function(game1, game2){
        if(game1.title < game2.title) {return -1; }
        if(game1.title > game2.title) {return 1; }
        return 0; 
      }); 
    }
    return user_games.map( game => 
      <Card key={game.id}>
        <Card.Content>
          <Card.Header>{game.title}</Card.Header>
          <Card.Description>Players: {game.min_players} - {game.max_players}</Card.Description>
          <Card.Description>Company: {game.company}</Card.Description>
          <Card.Description>Time Needed: {game.time_needed}</Card.Description>
        </Card.Content>
        <Card.Content extra> 
              <Button basic color='red' onClick={() => this.removeGame(game.id)}>
                Remove from Library
              </Button>
          </Card.Content>
      </Card> 
    )
  }

  gamesList = () => {
//gives each game with a link to more info
    const { games, user_games } = this.state 
    return games.map( game =>
        <Card key={game.id}>
          <Card.Content>
            <Card.Header>{game.title}</Card.Header>
            <Card.Description>Players: {game.min_players} - {game.max_players}</Card.Description>
            <Card.Description>Company: {game.company}</Card.Description>
            <Card.Description>Time Needed: {game.time_needed}</Card.Description>
          </Card.Content>
          { user_games.include ? (
          <Card.Content extra>
              <Button basic color='green' onClick={() => this.addGame(game.id)}>
                Add to Library
              </Button>
          </Card.Content>
          ) 
            : (
          <Card.Content extra> 
              <Button basic color='red' onClick={() => this.removeGame(game.id)}>
                Remove from Library
              </Button>
          </Card.Content>
          )  
          }
        </Card> 
      )
  }

  render() {
    const { showGames } = this.state 
    return (
      <Container>
        <h1>Games</h1>
        <Grid>
          <Grid.Column floated="left" width={2}>
            <h3>Your Games</h3>
          </Grid.Column> 
          <Grid.Column floated="right" width={2}>
            {this.dropDownMenu()}
          </Grid.Column>
        </Grid>
        <Card.Group itemsPerRow={4}>{this.userLibrary()}</Card.Group>
        { showGames ? (
            <div>
              <Button basic onClick={this.toggleGames}>Done Adding</Button>
              <Card.Group itemsPerRow={4}>{this.gamesList()}</Card.Group> 
            </div>
        )
          : (
          <Button basic onClick={this.toggleGames}>Add a Game</Button>
        ) 
        }
      </Container>
    )
  }
}

const mapStateToProps = state => {
  return { user: state.user };
};

export default connect(mapStateToProps)(Games);

这是棋盘游戏控制器:

class Api::BoardGamesController < ApplicationController
  before_action :set_board_game, except: [:index]


  def index
    render json: BoardGame.all
  end

  def show
    render json: @board_game
  end

  def create
    board_game = BoardGame.new(board_game_params)
    if board_game.save
      render json: board_game 
    else
      render json: board_game.errors
    end 
  end

  def update
    if @board_game.update(board_game_params)
      render json: @board_game 
    else 
      render_error(@board_game)
    end 
  end

  def destroy 
    binding.pry 
    @board_game.destroy 
  end 

  private 

  def set_board_game 
    @board_game = BoardGame.find(params[:id])
  end 

  def board_game_params
    params.require(:board_game).permit(
    :title,
    :min_players,
    :max_players,
    :base_game,
    :time_needed,
    :company 
    )
  end 

end

棋盘游戏模型:

class BoardGame < ApplicationRecord
  has_many :game_sessions, through: :game_session_games 
  has_many :users, through: :user_board_games
  has_many :rounds 
end

用户模型:

class User < ActiveRecord::Base
  has_many :board_games, through: :user_board_games
  has_many :game_sessions 
  # User.joins(:board_games).where("board_games.id == 'user.id'")
  # Include default devise modules. Others available are:
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  include DeviseTokenAuth::Concerns::User
end

userBoardGame 模型:

class UserBoardGame < ApplicationRecord
belongs_to :user 
belongs_to :board_game   
end

那么 user_board_games_controller.rb 中的内容是什么?

class Api::UserBoardGamesController < ApplicationController
  def destroy 
    @user.board_game.destroy 
  end 

end

【问题讨论】:

  • 您可能不需要显示那么多代码。当用户有很多棋盘游戏时,@user.board_game 也没有意义

标签: ruby-on-rails reactjs


【解决方案1】:

您需要在模型中将 user_board_games 设置为下面的示例代码

board_games 模型

class BoardGame < ApplicationRecord

  # I think you should mention user_board_games here
  has_many :user_board_games, :dependent => :destroy
  has_many :users, through: :user_board_games # you already had this line
end

与用户模型相同

class User < ActiveRecord::Base
  # here is additional code
  has_many :user_board_games, :dependent => :destroy
  has_many :board_games, through: :user_board_games
end

如果您想断开某个用户与特定 board_games 的连接,那么您可以从 UserBoardGamesController 中进行操作

class Api::UserBoardGamesController < ApplicationController

  def destroy 
    # you need two inputs here user_id and board_game_id
    @user = User.find(params[:user_id])
    # here is why we set has_many for user_board_games above
    @user_board_game = @user.user_board_games.find(params[:board_game_id])
    @user_board_game.destroy 
  end 

end

后续问题为什么User和BoardGame都要提到has_many UserBoardGames,这里是rails guide reference for more detail and sample reference,基本上是通知rails根据保存在UserBoardGames中的外键进行查询,

UserBoardGames 将有 user_id 和 board_game_id 字段,例如 id 为 1 的用户,有 2 个棋盘游戏(id 2 和 3),UserBoardGames 将保存 2 条记录如下

| user_id | board_game_id |
|---------|---------------|
| 1       | 2             |
| 1       | 3             |

当你给出命令@user.board_games时,它会首先在UserBoardGame中查询上面的2条记录,然后继续查找id为2和3的BoardGame记录,这里的关键是User和BoardGame之间的连接是UserBoardGame记录

【讨论】:

  • 感谢您的回复!我会试试看。我很好奇为什么 BoardGame 和 User has_many :user_board_games?
  • 我在上面解释过,因为解释有点长(请参阅我的部分后续问题)
  • 感谢您对该问题的解释!所以我进入并进行了这些更改,但我碰巧在我的 board_games_controller 的销毁函数中有一个 binding.pry,并注意到当我单击用户库中的“删除游戏”时我点击了它。这应该触发了 axios.delete 路由到用户的棋盘游戏,但它却从主数据库中删除了游戏。有什么想法吗?
  • 这可能必须转到 user_board_games 而不是棋盘游戏 这里是示例 axios.delete(/api/users/${userId}/user_board_games/${id})
猜你喜欢
  • 1970-01-01
  • 2016-04-01
  • 2018-11-10
  • 1970-01-01
  • 2019-03-06
  • 1970-01-01
  • 2013-02-03
  • 2014-09-29
  • 1970-01-01
相关资源
最近更新 更多