"Next" button creates a Viewed_Lesson | Ruby on Rails 4 | Learning App - ruby-on-rails

I am trying to make a "next" button that should generate a Viewed_lesson (user_id, lesson_id, boolean: true) and then redirect to the next lesson. The purpose is to make a tracking progress to show the progress of the user over a course.
My models:
class Course
has_many :lessons
end
class Lesson
#fields: course_id
belongs_to :course
end
class User
has_many :viewed_lessons
has_many :viewed_courses
end
class ViewedLesson
#fields: user_id, lesson_id, completed(boolean)
belongs_to :user
belongs_to :lesson
end
class ViewedCourse
#fields: user_id, course_id, completed(boolean)
belongs_to :user
belongs_to :course
end
What would the create action of viewed_lesson controller should look like?
There is only a create method needed.
And how would the button/form would look like in the view? The button is placed in lectures/id/lesson/id
Thank you for your help in advance!

I would say that your design sounds a little bit off. In rails there is not always a 1:1 crud relation between models and controllers.
First off I would rename those models UserLesson and UserCouse. It makes the relation really obvious and ViewedLesson stinks because it implies some sort of state.
In Rails a 1:1 relation between models and controllers is not always the best solution. And sometimes just using the standard crud verbs might not cut it.
Imagine the follow scenarios:
Does POST /user/1/lessons mean that a user has completed a course?
Or does it mean the user has just started a lesson?
Instead you might want to do a design like the following:
# config/routes.rb
resources :lessons # Used by admins to CRUD the curriculum.
resources :users
resources :lessons, controller: `users/lessons` do
member do
post :complete
end
end
end
# app/controllers/users/lessons_controller.rb
module Users
class LessonsController
before_action :set_lesson, only: [:show, :update, :complete]
# ...
# Enroll user in a lession
# POST /users/:user_id/lessons/:id
def create
#user_lesson = UserLesson.create(lesson: params[:id], user: params[:user_id])
end
# POST /users/:user_id/lessons/:id/complete
def complete
# #todo check if user has completed all steps...
end
# Save user progress when they reach certain milestones
def update
# #todo update user_lesson
end
def set_lesson
#lesson = Lession.find(params[:id])
end
def set_user_lesson
#user_lesson = UserLesson.find_by(user: params[:user_id], lession_id: params[:id])
end
end
end
Another alternative would be if you don't want to nest the routes under users:
resources :lessions, controller: 'user_lessions' do
member do
post :complete
end
end
# or
resources :courses, controller: 'user_courses' do
resources :lessions, controller: 'user_lessions' do
member do
post :complete
end
end
end
namespace :admin do
resources :lessions # used to crud the corriculum
end

It should look like following:
class ViewedLessonsController < ApplicationController
before_filter :set_user_and_lesson
def create
#viewed_lesson = ViewLession.new(user_id: #user.id, lession_id: #lesson.id, completed: true)
if #viewed_lesson.save
# redirect_to appropriate location
else
# take the appropriate action
end
end
end
In your ApplicationController class, you can do:
def set_user_and_lesson
#user = User.find_by_id(params[:user_id])
#lesson = Lesson.find_by_id(params[:lesson_id])
end
You also need to define the routes for it, and remember, you need to have models as well for ViewedLesson and ViewedCourse.

Related

I need a fresh pair of airs for this error here: "undefined method `items' for #<Order:0x00007f93d361b390>"

I am learning Ruby and building a shopping cart site, for book.
I am trying to create my orders. Was following the tutorial below.
https://www.youtube.com/watch?v=orDmqI-dlCo&list=PLebos0ZZRqzeT7XBTYwL9JGN1PwoZVB9p&index=4
The Error happens when I click on Add order to basket, the problem is that "items" is only referrenced on the routes and not on the tables, I have order_items table in my schema, I must be tired, but if you can help will appreciate.
book = Book.find(book_id)
order_item = order.items.find_or_create_by(
book_id: book_id
)
Order Items Controller
class OrderItemsController < ApplicationController
def create
current_bag.add_item(
book_id: params[:book_id],
quantity: params[:quantity]
)
redirect_to bag_path
end
end
Shopping Bag Controller
class ShoppingBag
def initialize(token:)
#token = token
end
def order
#order ||= Order.find_or_create_by(token: #token) do | order|
order.sub_total = 0
end
end
def add_item(book_id:, quantity: 1)
book = Book.find(book_id)
order_item = order.items.find_or_create_by(
book_id: book_id
)
order_item.price = book.price
order_item.quantity = quantity
order_item.save
end
end
Routes
Rails.application.routes.draw do
root "books#index"
resources :line_items
get '/bag', to: 'order_items#index'
resources :order_items, path: '/bag/items'
# resources :locations
resources :books
devise_for :users, controllers: {registrations: "registrations"}
resources :pricing, only: [:index]
resources :subscriptions
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
My Order.rb file
class Order < ApplicationRecord
end
The tutorial probably added the needed associations between the first and second video (like mentioned in part 1 timestamp 36:20).
With your current code I would assume they are as follows:
class Order < ApplicationRecord
has_many :items, class_name: 'OrderItem'
# ...
end
class OrderItem < ApplicationRecord
belongs_to :order
# ...
end
For this to work the order_items table must have an order_id column (with the same type as the id column of the orders table).
For more info about associations check out the Active Record Associations guide.

Polymorphic Routes with Custom Actions in Rails

So I am making a chat app, and I want users to be able to leave chat rooms. This would be done by posting a delete request to a route like /users/:id/chat_rooms/leave/:chat_room_id.
The Users model has has_many :chat_rooms, through: chat_room_users while ChatRooms has has_many :users, through chat_room_users. The UsersController has a leave action, which I want to call using this request on this url.
I want to create a link to this url on a view that I have. I already have a variable #user for the current user and #chosen for the current chat room available on the view. So how would I do a link_to and route for this setup? I have delete /users/:id/chat_rooms/leave/:chat_room_id in the routes.rb file, but how would I do the link_to?
Thank you.
You're overcomplicating it.
DELETE /chat_rooms/:chat_room_id/leave
Instead of passing the user id via the URL you should instead get it through the session or a token (if its an API app).
Rule of thumb: resources should never be nested more than 1 level
deep. A collection may need to be scoped by its parent, but a specific
member can always be accessed directly by an id, and shouldn’t need
scoping (unless the id is not unique, for some reason).
http://weblog.jamisbuck.org/2007/2/5/nesting-resources
This is just a loose example of how to solve this:
# routes.rb
resources :chat_rooms do
member do
post :join
delete :leave
end
end
class User
has_many :chat_room_users
has_many :chat_rooms, though: :chats
end
class ChatRoomUser
belongs_to :user
belongs_to :chatroom
end
class ChatRoom
has_many :chat_room_users
has_many :users, though: :chats
end
Putting this in UsersController is pretty questionable. I would instead place it in ChatroomsController.
class ChatroomsController
# ...
# POST /chat_rooms/:chat_room_id/join
def join
#chat_room = ChatRoom.find(params[:id])
#chat = current_user.chat_room_users.new(chat_room: #chat_room)
if #chat_room.create
# ...
else
# ...
end
end
# DELETE /chat_rooms/:chat_room_id/leave
def leave
#chat_room = ChatRoom.find(params[:id])
#chat = current_user.chat_room_users.find_by(chat_room: #chat_room)
#chat.destroy
end
end
<%= button_to 'Join', join_chat_room_path(#chat_room), method: :post %>
<%= button_to 'Leave', leave_chat_room_path(#chat_room), method: :delete %>

How to Create Basket of Shop site model in rails 4?

My project is about an online shopping site, using Ruby on Rails to buy phones.
My Database is User, Product, Phone.
I'm trying to create Basket model.
My route:
resources :products do
resources :phone do
resources :baskets
end
end
And my Code is:
class User < ActiveRecord::Base
has_many :baskets
end
class Phone < ActiveRecord::Base
belongs_to :product
has_many :baskets
end
class Basket < ActiveRecord::Base
belongs_to :user
belongs_to :phone
end
When i in the Show action of Product,it Show name of Product and index Phones in this Product,i want to add 1 Phone to Basket,the error is :
No route matches {:action=>"new", :controller=>"baskets", :id=>"38", :product_id=>"30"} missing required keys: [:phone_id]
I think the problem is :
http://localhost:3000/products/30/phone/38
It's Product_id = 30,but not Phone_id = 30,in here just is Id = 30.
Someone could help me fix it !
resources :products do
resources :phone do
resources :baskets
end
end
means you have to have route like this:
/products/:product_id/phones/:phone_id/baskets/:basket_id(.:format)
Which means, that in link_to you should pass the phone_id as well:
link_to 'show basket' product_phone_basket_path(product_id: #product.id, phone_id: #phone.id, basket_id: #basket.id)
link_to 'New basket' new_product_phone_basket_path(product_id: #product.id, phone_id: #phone.id)
Regardless of whether you got it working (I upvoted #Andrey's answer), you'll want to consult your routing structure.
Resources should never be nested more than 1 level deep. docs
--
In your case, I am curious as to why you have phones nested inside products. Surely a phone is a product?
Further, why are you including resources :baskets? Surely the basket functionality has nothing to do with whether you're adding a product, phone, or anything else?
I would personally do the following:
resources :products, only: [:index, :show] do
resources :basket, path:"", module: :products, only: [:create, :destroy] #-> url.com/products/:product_id/
end
#app/controllers/products/basket_controller.rb
class Products::BasketController < ApplicationController
before_action :set_product
def create
# add to cart
end
def destroy
# remove from cart
end
private
def set_product
#product = Product.find params[:product_id]
end
end
I've implemented a cart (based on sessions) before (here).
I can give you the code if you want; I won't put it here unless you want it. It's based on this Railscast.

Scoping resource routes in Rails 4

I have a resources :shops
which results in a /shops, /shops/:id, etc
I know I can scope collection or members with
resources :shops do
scope ":city" do
# collection and members
end
end
or do it before with
scope ":city" do
resources :shops
end
But I can't figure out how to make the route be on all members (including the standard REST ones) and collection, like so
/shops/:city/
/shops/:city/:id
As per your use case and question, you are trying to have logically wrong routes. You have shops within the city, NOT city within the shop.
Firstly you should normalize your database. You should create another table cities and replace your city attributes with city_id in shops table.
You need has_many and belongs_to association between cities and shops.
# Models
class City < ActiveRecord::Base
has_many :shops
... # other stuff
end
class Shop < ActiveRecord::Base
belongs_to :city
... # other stuff
end
Routes
resources :cities do
resources :shops
end
It will generate routes like:
POST /cities/:city_id/shops(.:format) shops#create
new_city_shop GET /cities/:city_id/shops/new(.:format) shops#new
edit_city_shop GET /cities/:city_id/shops/:id/edit(.:format) shops#edit
city_shop GET /cities/:city_id/shops/:id(.:format) shops#show
PATCH /cities/:city_id/shops/:id(.:format) shops#update
PUT /cities/:city_id/shops/:id(.:format) shops#update
DELETE /cities/:city_id/shops/:id(.:format) shops#destroy
Logically, these routes will show that in which city the particular shop exists.
Namespace
You may wish to consider including a namespace
Since you're trying to pull up the cities for shops (IE I imagine you want to show shops in Sao Paulo), you'd be able to do this:
#config/routes.rb
namespace :shops do
resources :cities, path: "", as: :city, only: [:index] do #-> domain.com/shops/:id/
resources :shops, path: "", only: [:show] #-> domain.com/shops/:city_id/:id
end
end
This will allow you to create a separate controller:
#app/controllers/shops/cities_controller.rb
Class Shops::CitiesController < ApplicationController
def index
#city = City.find params[:id]
end
end
#app/controllers/shops/shops_controller.rb
Class Shops::ShopsController < ApplicationController
def show
#city = City.find params[:city_id]
#shop = #city.shops.find params[:id]
end
end
This will ensure you're able to create the routing structure you need. The namespace does two important things -
Ensures you have the correct routing structure
Separates your controllers

remove specific user from joined table

In Ruby on Rails I have a user models and a jobs model joined through a different model called applicants. I have a button for the users when they want to "remove their application for this job" but I don't know how to remove the specific user, and for that matter I don't know if I'm doing a good job at adding them either (I know atleast it works).
user.rb
class User < ActiveRecord::Base
...
has_many :applicants
has_many:jobs, through: :applicants
end
job.rb
class Job < ActiveRecord::Base
...
has_many :applicants
has_many:users, through: :applicants
end
applicant.rb
class Applicant < ActiveRecord::Base
belongs_to :job
belongs_to :user
end
when someone applies for a job my jobs controller is called:
class JobsController < ApplicationController
...
def addapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id)
redirect_to #job
end
...
end
Does that .update indicate that whatever is there will be replaced? I'm not sure if I'm doing that right.
When someone wants to remove their application I want it to go to my jobs controller again but I'm not sure what def to make, maybe something like this?
def removeapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id).destroy
redirect_to #job
end
does it ave to sort through the list of user_ids save them all to an array but the one I want to remove, delete the table then put them all back in? I'm unsure how this has_many works, let alone has_many :through sorry for the ignorance!
thanks!
Let's assume the user will want to remove their own application. You can do something like this:
class UsersController < ApplicationController
def show
#applicants = current_user.applicants # or #user.find(params[:id]), whatever you prefer
end
end
class ApplicantsController < ApplicationController
def destroy
current_user.applications.find(params[:id]).destroy
redirect_to :back # or whereever
end
end
And in your view:
- #applicants.each do |applicant|
= form_for applicant, method: :delete do |f|
= f.submit
Don't forget to set a route:
resources :applicants, only: :destroy
Some observations, I would probably name the association application instead of applicant. So has_many :applications, class_name: 'Applicant'.

Resources