<file_sep>class Api::V1::ReviewsController < ApplicationController

    def index
        if params[:book_id]
            @reviews = Book.find(params[:book_id]).reviews
        render json: ReviewSerializer.new(@reviews)
        else 
            render json: {
                error: "Must have a book to see reviews"
            }
        end
    end 

    def show 
        review_json = ReviewSerializer.new(@review).serialized_json
        render json:review_json
    end 
        
    def create
        @review = Review.new(review_params)
        if @review.save
            render json: ReviewSerializer.new(@reviews), status: :created
          else
            error_resp = {
              error: @review.errors.full_messages.to_sentence
            }
            render json: error_resp, status: :unprocessable_entity
          end
        end 

     def update
        if @review.update(review_params)
          render json: @review
        else
          render json: @review.errors, status: :unprocessable_entity
        end
      end

    def destroy
      @review.destroy
    end

    private
      def set_review
        @review = Review.find(params[:id])
      end

      def review_params
        params.require(:review).permit(:content, :rating, :user_id, :book_id)
      end

  end
<file_sep>import React from "react";
import { connect } from "react-redux";
import { updateLoginForm } from "../actions/loginForm.js";
import { login } from "../actions/currentUser.js";

const Login = ({ loginFormData, updateLoginForm, login }) => {
  const handleInputChange = event => {
    const { name, value } = event.target;
    const updatedFormInfo = {
      ...loginFormData,
      [name]: value
    };
    updateLoginForm(updatedFormInfo);
  };

  const handleSubmit = event => {
    event.preventDefault();
    login(loginFormData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        placeholder="username"
        value={loginFormData.username}
        name="username"
        type="text"
        onChange={handleInputChange}
      />
      <input
        placeholder="<PASSWORD>"
        value={loginFormData.password}
        name="<PASSWORD>"
        type="text"
        onChange={handleInputChange}
      />
      <input type="submit" value="Log In" />
    </form>
  );
};

const mapStateToProps = state => {
  return {
    loginFormData: state.loginForm
  };
};

export default connect(
  mapStateToProps,
  { updateLoginForm, login }
)(Login);
<file_sep>import React from "react";
import MyBooks from "./MyBooks.js";

const MainContainer = () => {
  return (
    <div className="MainContainer">
      <MyBooks />
    </div>
  );
};

export default MainContainer;
<file_sep>class UserSerializer
  include FastJsonapi::ObjectSerializer
  attributes :username, :name
  attribute :books do |user|
    {
      books: user.books
    
    }
  end
  
end
<file_sep>import React from "react";
import "./App.css";
import { connect } from "react-redux";
import { getCurrentUser } from "./actions/currentUser.js";
import NavBar from "./components/NavBar.js";
import Login from "./components/Login.js";
import MyBooks from "./components/MyBooks.js";
import BookCard from "./components/BookCard.js";
import NewBookFormWrapper from "./components/NewBookFormWrapper.js";
import EditBookFormWrapper from "./components/EditBookFormWrapper";
import { Route, Switch, withRouter } from "react-router-dom";
import { deleteBook } from "./actions/myBooks";

class App extends React.Component {
  componentDidMount() {
    this.props.getCurrentUser();
  }
  render() {
    const { loggedIn, books } = this.props;
    return (
      <div className="App">
        {loggedIn ? <NavBar /> : <Login />}
        <Switch>
          <Route exact path="/login" component={Login} />
          <Route exact path="/books" component={MyBooks} />
          <Route exact path="/books/new" component={NewBookFormWrapper} />
          <Route
            exact
            path="/books/:id"
            render={props => {
              const book = books.find(
                book => book.id === props.match.params.id
              );
              return (
                <BookCard
                  book={book}
                  deleteBook={this.props.deleteBook}
                  {...props}
                />
              );
            }}
          />

          <Route
            exact
            path="/books/:id/edit"
            render={props => {
              const book = books.find(
                book => book.id === props.match.params.id
              );
              return <EditBookFormWrapper book={book} {...props} />;
            }}
          />
        </Switch>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    loggedIn: !!state.currentUser,
    books: state.myBooks
  };
};

export default withRouter(
  connect(
    mapStateToProps,
    { getCurrentUser, deleteBook }
  )(App)
);







<file_sep>class ReviewSerializer
  include FastJsonapi::ObjectSerializer
  attributes :content, :rating
  belongs_to :user
  belongs_to :book
  
end
<file_sep>import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import Card from "react-bootstrap/Card";

class MyBooks extends React.Component {
  render() {
    const bookCards = this.props.books.map((b) => (
      <>
        <Card
          key={b.id}
          b={b}
          style={{
            width: "16rem"
          }}
        >
          <Card.Body>
            <Card.Title>"{b.attributes.name}"</Card.Title>
            <Card.Text>By: {b.attributes.author}</Card.Text><Link to={`/books/${b.id}`}>...more</Link>
          </Card.Body>
        </Card>
        <br />
      </>
    ));

    return (
      <div>
        <h1 align="center">All Books:</h1>
        <div
          style={{
            display: "flex",
            flexWrap: "wrap",
            justifyContent: "space-between"
          }}
        >
          {bookCards}
        </div>
      </div>
    );
  }
}
const mapStateToProps = state => {
  return {
    books: state.myBooks
  };
};

export default connect(
  mapStateToProps
)(MyBooks);


<file_sep>class Book < ApplicationRecord
  has_many :reviews
  has_one :user
  
  validates :name, presence: true
end
<file_sep>class Api::V1::BooksController < ApplicationController
  before_action :set_book, only: [:show, :update, :destroy]

    def index
        if logged_in?
            @books = current_user.books    
        render json: BookSerializer.new(@books)
        else 
            render json: {
                error: "Must be logged in to see books"
            }
      end
    end 

     def show
        render json: @book
      end

    def create
      @book = current_user.books.build(book_params)

    if @book.save
      render json: BookSerializer.new(@book), status: :created
    else
      error_resp = {
        error: @book.errors.full_messages.to_sentence
      }
      render json: error_resp, status: :unprocessable_entity
    end
  end
     
    def update
        if @book.update(book_params)
          render json: BookSerializer.new(@book), status: :ok
         
    else
      error_resp = {
        error: @book.errors.full_messages.to_sentence
      }
      render json: error_resp, status: :unprocessable_entity
    end
  end

    def destroy 
      if @book.destroy
        render json:  { data: "Book successfully destroyed" }, status: :ok
      else
        error_resp = {
          error: "Book not found and not destroyed"
        }
        render json: error_resp, status: :unprocessable_entity
      end
    end
    
    private

    def set_book
      @book = Book.find(params[:id])
    end

    def book_params
      params.require(:book).permit(
          :name,
          :author,
          :description,
          :user_id
        )
    end    
 end 
<file_sep># Book Fellow
[ NOTE ]: I will return to this app to clean up the code and add more features.

## Background
Book social application is for children's books and beginner readers, user can CRUD and recommend books for other parents/kids. Also user can read other's book suggestions. 
This application is using React, Redux, Ruby on Rails, HTML, Javascript and Materialize CSS framework.

## Installation Instructions

### Setup Rails API backend:

Clone this repo, and navigate to the repo directory in your terminal

Change directories into backend folder: cd book-fellow-backend

Run bundle install to install all gem dependencies

Also run all rake migrations with rake db:migrate

Then load rails s to start

If you want to see this app in the browser, open your browser and navigate to `http://localhost:3000` (press ctrl-C or control-C while in the terminal to exit).

### Setup React server client side:

Change directories into client folder: cd book-fellow-frontend

Run npm install to install all package dependencies

Run npm install es6-promise

Run Rake Start to start up both servers

Open your browser and navigate to `http://localhost:3001` (press ctrl-C or control-C while in the terminal to exit)

## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/yanchanllin/book-fellow.git. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

## License
The gem is available as open source under the terms of the MIT License.

<file_sep>import React from "react";
import BookForm from "./BookForm";
import { createBook } from "../actions/bookForm";
import { connect } from "react-redux";

const NewBookFormWrapper = ({ history, createBook }) => {
  const handleSubmit = (formData, userId) => {
    createBook(
      {
        ...formData,
        userId
      },
      history
    );
  };
  return <BookForm history={history} handleSubmit={handleSubmit} />;
};

export default connect(
  null,
  { createBook }
)(NewBookFormWrapper);
<file_sep>import { resetLoginForm } from "./loginForm.js";
import { getMyBooks, clearBooks } from "./myBooks.js";

//synchronous action creators
export const setCurrentUser = user => {
  return {
    type: "SET_CURRENT_USER",
    user
  };
};

export const clearCurrentUser = () => {
  return {
    type: "CLEAR_CURRENT_USER"
  };
};

//asynchronous action creators
export const login = credentials => {
  // console.log("credentials are", credentials);
  return dispatch => {
    return fetch("https://murmuring-atoll-04944.herokuapp.com/api/v1/login", {
      credentials: "include",
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(credentials)
    })
      .then(r => r.json())
      .then(response => {
        if (response.error) {
          alert(response.error);
        } else {
          dispatch(setCurrentUser(response.data));
          dispatch(getMyBooks());
          dispatch(resetLoginForm());
        }
      })
      .catch(console.log);
  };
};

export const logout = event => {
  return dispatch => {
    dispatch(clearCurrentUser());
    dispatch(clearBooks());
    return fetch("https://murmuring-atoll-04944.herokuapp.com/api/v1/logout", {
      credentials: "include",
      method: "DELETE"
    });
  };
};

export const getCurrentUser = () => {
  return dispatch => {
    return fetch("https://murmuring-atoll-04944.herokuapp.com/api/v1/get_current_user", {
      credentials: "include",
      method: "GET",
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(r => r.json())
      .then(response => {
        if (response.error) {
          alert(response.error);
        } else {
          dispatch(setCurrentUser(response.data));
          dispatch(getMyBooks());
        }
      })
      .catch(console.log);
  };
};
<file_sep># This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
#   movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
#   Character.create(name: 'Luke', movie: movies.first)


# lili = User.first
User.create(name:"lilil", username:"lilil", password:"<PASSWORD>")
# amy = User.second
# amy.books.create(name: "Blue", author:"<NAME>", description:"It's good for age 4 & up")
 
# lili.books.create(name: "The Tiny Seed", author:"<NAME>", description:"age 4 & up,Its journey is perilous, and the reader learns the fate of fellow seeds")
# lili.reviews.create(content:"good for beginnner reading", rating:"4/5", user_id:1,book_id:1)
# amy.reviews.create(content:"Each page is packed with intriguing facts, future places, and lots more!", rating:"4/5",user_id:1,book_id:2)<file_sep>class BookSerializer
  include FastJsonapi::ObjectSerializer
  attributes :name, :author, :description, :user_id
  attribute :reviews do |book|
    book.reviews.map do |rev|
      {
        content: rev.content,
        rating: rev.rating
      }
    end
  end

end
<file_sep>BOOK SOCIAL APP (for children's books-educational, beginner readings)
A REACT APP WITH A RAILS API BACKEND
2019-06

\*USER
name
username
password_digest

has_many :books
has_many :reviews

\*BOOK
name,author,description
has_many :reviews
has_many :users

\*REVIEW
content,rating
belongs_to :user
belongs_to :book

rails new book-fellow --api
rails g scaffold user name username password_digest

Domain ideas:
bank app
house sale
apps review

Overview
It's mostly for children's books-educational, beginner readings. User can list many books and review on them, recommend for other parents/kids. User can also read other's book suggestions.
<file_sep>import React from "react";
import BookForm from "./BookForm";
import { updateBook } from "../actions/bookForm";
import { setFormDataForEdit, resetNewBookForm } from "../actions/bookForm";
import { connect } from "react-redux";

class EditBookFormWrapper extends React.Component {
  componentDidMount() {
    this.props.book && this.props.setFormDataForEdit(this.props.book);
  }

  componentDidUpdate(prevProps) {
    this.props.book &&
      !prevProps.book &&
      this.props.setFormDataForEdit(this.props.book);
  }

  componentWillUnmount() {
    this.props.resetNewBookForm();
  }

  handleSubmit = (formData, userId) => {
    const { updateBook, book, history } = this.props;

    updateBook(
      {
        ...formData,
        bookId: book.id,
        userId
      },
      history
    );
  };

  render() {
    return <BookForm editMode handleSubmit={this.handleSubmit} />;
  }
}

export default connect(
  null,
  { updateBook, setFormDataForEdit, resetNewBookForm }
)(EditBookFormWrapper);
