I am attempting to allow a user that is logged in to create a review for a game. I am having a couple issues that keep popping up in my console.
HTTP Origin header (http://localhost:3001) didn't match request.base_url (http://localhost:3000)
I attempted to remedy this with putting config.force_ssl = true in my production file from what I read up on, but still hitting this issue.
Im also hitting
NoMethodError (undefined method `id' for nil:NilClass):
which is ref my review_controller in create
app/controllers/reviews_controller.rb:19:in `create'
Below is my ReviewController and my ReviewContainer and ReviewForm
class ReviewsController < ApplicationController
# before_action :authorized, only:[:create]
before_action :authenticate_with_http_digest, only: [:new, :create]
def index
reviews = Review.all
render json: reviews
end
def show
review = Review.find_by(params[:id])
render json: review
end
def create
game = Game.find_or_create_by(name: params[:review][:game_name])
review = Review.new(review_params)
review.game_id = game.id
review.user_id = #user.id
review.save
render json: review
end
def update
review = Review.find(params[:id])
review.update(review_params)
review.save
render json: review
end
def destroy
review = Review.find(params[:id])
review.destroy
render json: {error: "Review Removed"}
end
private
def review_params
params.require(:review).permit(:user_id, :game_id, :user_username, :reviewed_game, :rating, :game_name)
end
end
import React, { Component } from 'react'
import Review from './Review'
import ReviewForm from './ReviewForm'
export default class ReviewsContainer extends Component {
state = {
reviews: [],
}
componentDidMount(){
fetch('http://localhost:3000/reviews')
.then(res => res.json())
.then(reviews => this.setState({ reviews }))
}
addReview = (review) => {
fetch('http://localhost:3000/reviews',{
method: "POST",
headers: {
"Content-Type" : "application/json",
Accept: "application/json",
Authorization: `bearer ${localStorage.token}`
},
body: JSON.stringify({ review: review }
),
})
.then(res => res.json())
.then(( json => {
this.setState(prevState => ({
reviews: [...prevState.reviews, json ]
}))
}))
}
// handleShowForm = () => {
// this.setState({edit: false})
// }
render() {
return (
<div className="review-grid">
<ReviewForm addReview={this.addReview} review={this.handleSubmit} />
<h1 className="review-content">REVIEWS!</h1>
<ul className="review-cards">
{
this.state.reviews.map(review => <Review key={review.id} review={review}/>)
}
</ul>
</div>
)
}
}
import React, { Component } from 'react'
class ReviewForm extends React.Component {
state = {
reviewed_game: '',
rating: '',
user_username: '',
}
handleReviewedGame = (event) => {
this.setState ({
reviewed_game: event.target.value
})
}
handleRating = (event) => {
this.setState ({
rating: event.target.value
})
}
handleUser = (event) => {
this.setState ({
user_username: event.target.value
})
}
handleForm = (e) => {
e.preventDefault()
// console.log(e)
const review = {
reviewed_game: this.state.reviewed_game,
rating: this.state.rating,
}
this.props.addReview(review)
}
render() {
return (
<div className="form-container">
<form onSubmit={(e) => {this.handleForm(e)}}>
<div>
<label>Review</label>
<br></br>
<textarea type="text" placeholder="Drop Your Review" rows={10} cols={50} value={this.state.reviewed_game} onChange={this.handleReviewedGame} className="form"/>
<div>
<label>Stars</label>
<br></br>
<input type="number" max="5" min="0" value={this.state.rating} onChange={this.handleRating} />
</div>
</div>
<button type="submit" className="sub-review">Create Review!</button>
</form>
</div>
)
}
}
export default ReviewForm;
Any advise on how to correct the issue is appreciated! thanks!
Related
I'm making an image library type thing in Rails and Vue, and I'm using DirectUpload to manage attachments.
# photo.rb
class Photo < ApplicationRecord
has_one_attached :file
# ...
end
# photos_controller.rb
class PhotosController < ApplicationController
load_and_authorize_resource
before_action :set_photo, only: %i[show update destroy]
protect_from_forgery unless: -> { request.format.json? }
def index
#photo = current_user.photos.new
render action: 'index'
end
def create
#photo = current_user.photos.create(photo_params)
render json: PhotoBlueprint.render(#photo, root: :photo)
end
# ...
def photo_params
params.require(:photo).permit(:id, :file)
end
end
# photos/index.html.erb
<%= simple_form_for(#photo, url: photos_path(#photo), html: { multipart: true }) do |f| %>
<%= f.file_field :file, multiple: true, direct_upload: true, style: 'display: none;' %>
<% end %>
<div id='photos-app'></div>
// UserFileLib.vue
<script>
import { mapState, mapActions } from 'pinia'
import { usePhotoStore } from '#stores/photo'
import { DirectUpload } from '#rails/activestorage'
export default {
name: 'UserFileLib',
computed: {
...mapState(usePhotoStore, [
'photos'
]),
url () {
return document.getElementById('photo_file').dataset.directUploadUrl
},
input () {
return document.getElementById('file-input')
},
},
mounted () {
this.getPhotos()
},
methods: {
...mapActions(usePhotoStore, [
'addPhoto',
'getPhotos',
]),
activestorageURL (blob) {
return `/rails/active_storage/blobs/redirect/${blob.signed_id}/${blob.filename}`
},
uploadToActiveStorage () {
const file = this.input.files[0]
const upload = new DirectUpload(file, this.url)
upload.create((error, blob) => {
if (error) {
console.log(error)
} else {
const url = this.activestorageURL(blob)
console.log(url)
this.getPhotos()
}
})
},
openFileBrowser () {
this.input.click()
},
formatSize (bytes) {
return Math.round(bytes / 1000)
}
}
}
</script>
<template>
<div
#click="openFileBrowser"
class="card p-3">
Click or drop files here
</div>
<input
type="file"
:multiple="true"
#change="uploadToActiveStorage"
id="file-input" />
<div class="grid is-inline-grid mt-2">
<div
class="image-container"
v-for="image in photos"
:key="image.id">
<img :src="image.url" :alt="image.label" />
<div class="filename">
<strong>{{ image.label }}</strong>
<br />
{{ formatSize(image.size) }} kb
</div>
<div class="close">
×
</div>
</div>
</div>
</template>
Now, the uploads work fine, the blob is stored correctly.
My issue is that a new Photo object is not created to wrap the attachment, meaning the uploads are lost in the system and have no parent record.
What am I doing wrong?
I've solved this for anyone else looking for help. The logic is to create or update the parent record after the upload is done. I missed this in the official documentation.
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// ** This is the way **
// Add an appropriately-named hidden input to the form with a
// value of blob.signed_id so that the blob ids will be
// transmitted in the normal upload flow
// ** End of **
//
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
}
})
Since I'm using Vue and Pinia I made a solution in keeping with that logic:
// UserImageLib.vue
uploadToActiveStorage (input, file) {
const url = input.dataset.directUploadUrl
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
console.log(error)
} else {
const params = { [input.name]: blob.signed_id }
this.createPhoto(params)
}
})
},
// stores/photo.js
addPhoto (payload) {
this.photos.unshift(payload)
},
createPhoto (payload) {
http.post(`/photos`, payload).then(res => {
const photo = res.data.photo
this.addPhoto(photo)
})
},
I am creating a detail screen with the app I am creating.
When transitioning to the detail screen, the contents of the data do not occur on the screen, and this error occurs in the console:
Error in render: "TypeError: Cannot read property 'title' of undefined".
code
<template>
<main>
<section>
<div>
<table>
<thead>
<tr>
<th>タイトル</th>
<th>詳細</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ latest_information.title }}</td>
<td>{{ latest_information.detail }}</td>
</tr>
</tbody>
</table>
</div>
</section>
</main>
</template>
<script>
export default {
data() {
return {
latest_information: {},
}
},
methods: {
setData() {
this.$loading.load(this.$auth.api.get(`admin/latest_informations/${this.$route.params.id}.json`)
.then(response => {
this.latest_information = response.data.latest_information
})
.catch(err => {
this.$errorHandlers.initial(err);
}));
},
},
mounted() {
this.setData();
},
}
</script>
command「rails routes」↓
api_v1_admin_latest_information GET /api/v1/admin/latest_informations/:id(.:format) api/v1/admin/latest_informations#show {:format=>:json}
latest_informations_controller.rb
module Api::V1::Admin
class LatestInformationsController < Api::V1::ApiController
before_action :set_latest_information, only: [:show, :destroy]
def show
render json: #latest_information
end
private
def set_latest_information
#latest_information = LatestInformation.find(params[:id])
end
end
end
show.json.jbuilder
json.set! :latest_information do
json.id #latest_information.id
json.title #latest_information.title
json.detail #latest_information.detail
end
Tried
・Put byebug in the controller,check the value of #latest_information
(byebug) #latest_information
#<LatestInformation id: 3, title: "���������", detail: "���������", created_at: "2021-08-01 22:01:39", updated_at: "2021-08-01 22:01:39">
・Check the JSON data created by jbuilder
irb(main):040:0> Jbuilder.encode do |json|
irb(main):041:1* json.set! :latest_information do
irb(main):042:2* json.id LatestInformation.find(3).id
irb(main):043:2> json.title LatestInformation.find(3).title
irb(main):044:2> json.detail LatestInformation.find(3).detail
irb(main):045:2> end
irb(main):046:1> end
LatestInformation Load (1.3ms) SELECT `latest_informations`.* FROM `latest_informations` WHERE `latest_informations`.`id` = 3 LIMIT 1
LatestInformation Load (1.0ms) SELECT `latest_informations`.* FROM `latest_informations` WHERE `latest_informations`.`id` = 3 LIMIT 1
LatestInformation Load (0.7ms) SELECT `latest_informations`.* FROM `latest_informations` WHERE `latest_informations`.`id` = 3 LIMIT 1
=> "{\"latest_information\":{\"id\":3,\"title\":\"てst\",\"detail\":\"てst\"}}"
Environment
rails 6
vue#2.6.10
postscript
Look at the contents of response and modify as follows.I got the value.
However, it is not displayed on the screen.
Show.vue
data() {
return {
latest_information: {
title: "",
detail: ""
},
}
},
methods: {
// api_v1_admin_latest_information GET /api/v1/admin/latest_informations/:id(.:format) api/v1/admin/latest_informations#show {:format=>:json}
setData() {
this.$loading.load(this.$auth.api.get(`admin/latest_informations/${this.$route.params.id}.json`)
.then(response => {
this.title = response.data.title
this.detail = response.data.detail
})
.catch(err => {
this.$errorHandlers.initial(err);
}));
},
},
mounted() {
this.setData();
},
}
I think this one is the cause
.then(response => {
this.title = response.data.title
this.detail = response.data.detail
})
it should
.then(response => {
this.title = response.latest_information.title
this.detail = response.latest_information.detail
})
or you can try to debug the response
.then(response => {
console.log(response)
})
I am having an issue with submitting my data from form on the front end. Everytime I submit the form I get a Unpermitted parameter: :recipe
I was told to make sure my attributes on my frontend matched what was on the backend strong params.
Here is what is in my controller for my create action and my strong params
class RecipesController < ApplicationController
def create
recipe = Recipe.create(recipe_params)
if recipe.save
render json: recipe
else
render json: { error: "Couldn't save" }
end
end
private
def recipe_params
params.permit(:category_id,:name,:ingredients,:chef_name,:origin,category_attribute:[:category])
end
end
And here is my React frontend where I am inputting the info in the form
Side note I took out the event handlers for this code snippet but left the submit handler just to keep this explanation shorter
import React, { Component } from 'react'
import Categories from './Categories.js'
class RecipeInput extends Component{
constructor(props){
super(props)
this.state = {
category: [],
categoryId: '',
name:'',
ingredients: '',
chef_name: '',
origin: ''
}
this.handleNameChange.bind(this)
this.handleOriginChange.bind(this)
this.handleChefChange.bind(this)
this.handleIngChange.bind(this)
}
componentDidMount(){
let initialCats = [];
const BASE_URL = `http://localhost:10524`
const CATEGOREIS_URL =`${BASE_URL}/categories`
fetch(CATEGOREIS_URL)
.then(resp => resp.json())
.then(data => {
initialCats = data.map((category) => {
return category
})
console.log(initialCats)
this.setState({
category: initialCats,
})
});
}
handleSubmit = (event) =>{
event.preventDefault();
this.props.postRecipes(this.state)
this.setState({
categoryId: '',
name:'',
ingredients: '',
chef_name: '',
origin: ''
})
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<Categories category={this.state.category}/>
<div>
<label for='name'>Recipe Name:</label>
<input type='text' value={this.state.name} onChange={this.handleNameChange} />
</div>
<div>
<label for='name'>Country Origin:</label>
<input type='text' value={this.state.origin} onChange={this.handleOriginChange} />
</div>
<div>
<label for='name'>Chef Name:</label>
<input type='text' value={this.state.chef_name} onChange={this.handleChefChange} />
</div>
<div>
<label for='name'>Ingredients:</label>
<textarea value={this.state.ingredients} onChange={this.handleIngChange} />
</div>
<input value='submit' type='submit'/>
</form>
</div>
)
}
}
export default RecipeInput
I am just a little clueless on where to go to from here. Am I matching the attributes correctly?
Edit
I forgot to include my postRecipes function with my dispatches in place
export const postRecipes = (recipe)=>{
const BASE_URL = `http://localhost:10524`
const RECIPES_URL =`${BASE_URL}/recipes`
const config = {
method: "POST",
body:JSON.stringify(recipe),
headers: {
"Accept": "application/json",
"Content-type": "application/json"
}
}
//category field
return(dispatch)=>{
fetch(RECIPES_URL,config)
.then(response =>
response.json())
.then(resp => {
dispatch({
type: 'Add_Recipe',
payload:{
category:resp.category,
name: resp.name,
ingredients: resp.ingredients,
chef_name: resp.chef_name,
origin: resp.origin,
categoryId: resp.categoryId
}
})
})
//.then(response => <Recipe />)
.catch((error) => console.log.error(error))
}
}
Edit
Here is my reducer that defines my Add-Recipe action for payload.
export default function manageRecipes(state={
recipes:[],
category:[],
}, action){
switch(action.type){
case 'Add_Recipe':
const recipe = {
name: action.name,
ingredients: action.ingredients,
chef_name: action.chef_name,
origin: action.origin,
categoryId: action.categoryId,
category: action.category,
// id: cuidFn()
}
return{
...state,
recipes: [...state.recipes, recipe],
}
case 'Delete_Recipe':
const recipes = state.recipes.filter(recipe => recipe.id !== action.id)
return {...state, recipes}
case 'Add_Catagory':
const cat = {
name: action.name
}
return{
...state,
category: [...state.category, cat],
}
default:
return state
}
}
try this in your backend
def recipe_params
params.require(:recipe).permit(
:category_id, :name,:ingredients, :chef_name,
:origin,category_attribute:[:category]
)
end
EDIT:
bwalshy - helped me solve the issue - I was rendering a static error message, instead of the actual server response. Once I rendered the server response it was obvious that I was trying to create the test account with the password as "123" which is too weak for the gem's standards. Coding is a humbling experience.
I've seen this question posted, but without any solutions that work for me. I have a React front end that is mounted on top of a Rails app. When I try to call my registrations controller I am getting the following error:
Completed 422 Unprocessable Entity in 103ms
I saw this this could be token related, and logging the token shows what appears to be a valid one: v4Ml1tlzkgBKWBcYGP9SGO+YVL7fxcxs3D8MhC3Z/3ZcZOa5rYGSHUABG+vL+yJfoVfO1Ks6RLI60sQDk7Hh8A==
which is being passed in the headers
The server response is:
{"error":"signup error"}
Here is the React component
import React from "react";
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
signupUnsuccessful: false,
email: '',
passwordOne: '',
passwordTwo: '',
error: null,
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
};
onChange = event => {
this.setState({ [event.target.name]: event.target.value });
}
//this is erroring and I think it's because I'm not telling devise to deliver json
onSubmit(event) {
event.preventDefault();
const url = "http://localhost:3000/users";
const { email } = this.state;
const password = this.state.passwordOne
const userInfo = {
user: {
email,
password
}
}
const token = document.querySelector('meta[name="csrf-token"]').content;
console.log(token)
fetch(url, {
method: "POST",
headers: {
"X-CSRF-Token": token,
"Content-Type": "application/json"
},
body: JSON.stringify(userInfo)
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.catch(error => console.log(error.message));
}
render() {
const {
email,
passwordOne,
passwordTwo,
error,
} = this.state;
const isInvalid =
passwordOne !== passwordTwo ||
passwordOne === '' ||
email === '';
return (
<div>
<h2>Signup</h2>
<form onSubmit={this.onSubmit}>
<input name="email"
value={email}
onChange={this.onChange}
type="text"
placeholder="Email Address"/>
<input name="passwordOne"
value={passwordOne}
onChange={this.onChange}
type="password"
placeholder="Password" />
<input name="passwordTwo"
value={passwordTwo}
onChange={this.onChange}
type="password"
placeholder="Confirm Password"/>
<button disabled={isInvalid} type="submit">
Sign Up
</button>
</form>
<button onClick={() => this.props.changePage("login")}>Login!</button>
</div>
);
};
};
export default SignUp;
and the controller:
class RegistrationsController < Devise::RegistrationsController
def create
#user = User.new(user_params)
if #user.save
render json: #user
else
warden.custom_failure!
render json: { error: 'signup error' }, status: :unprocessable_entity
end
end
def update
#user = User.find_by_email(user_params[:email])
if #user.update_attributes(user_params)
render json: #user
else
warden.custom_failure!
render :json=> #user.errors, :status=>422
end
end
def destroy
#user = User.find_by_email(user_params[:email])
if #user.destroy
render :json=> { success: 'user was successfully deleted' }, :status=>201
else
render :json=> { error: 'user could not be deleted' }, :status=>422
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
And my routes
Rails.application.routes.draw do
devise_for :users, controllers: { registrations: 'registrations', sessions: 'sessions' }
root 'homepage#index'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
This is the component I'm currently working on:
import React, { Component } from 'react';
import axios from 'axios';
import { connect } from 'react-redux';
import { Button, Card, Container } from 'semantic-ui-react'
class Games extends Component {
state = { games:[], user_games: [], showGames: false }
componentDidMount() {
const userId = this.props.user.id
axios.get('/api/board_games')
.then(res => {
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);
console.log(res.data);
})
}
addGame = (id) => {
const {userId} = this.props.user.id
axios.post(`api/users/${userId}/board_games`, { userId, id })
.then(res => {
console.log(res);
})
}
userLibrary = () => {
const {user_games} = this.state
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 = () => {
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>
<h3>Your Games</h3>
<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);
When I click on "Remove from Library" my server gives me:
NoMethodError (undefined method `destroy' for nil:NilClass):
app/controllers/api/board_games_controller.rb:30:in `destroy'
and console gives me:
xhr.js:178 DELETE http://localhost:3000/api/users/1/board_games/1 500 (Internal Server Error)
but my controller does in fact define 'destroy' :
class Api::BoardGamesController < ApplicationController
# before_action :set_board_game
def index
render json: BoardGame.all
end
def show
render json: #board_games
end
def create
board_game = BoardGame.new
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
#board_game.destroy
end
private
# def set_board_game
# #board_game = Board_Game.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
You'll notice the before_action and set_board_game is commented out. When I un-comment them, my axios.get fails as soon as the component tries to mount.
What am I doing wrong with my axios.destroy?
(and in general if you see anything else horribly wrong).
Your error states:
NoMethodError (undefined method `destroy' for nil:NilClass)
because in your destroy action:
def destroy
#board_game.destroy
end
you never instantiation #board_game.
As you note, you commented out def set_board_game, which might have done the job. But as it stands, #board_game is nil.
You'll need to uncomment that bit and fix your other comment about axious.get failing. Or, set the #board_game directly in your destroy method.
but my controller does in fact define 'destroy'
The error has nothing to do with the destroy action not being defined. It is referring, as previously stated, to #board_game being nil.