My goal is for users to add individual games pulled from an API gem (https://github.com/games-directory/api-giantbomb) to their personal library. I want users to be able to browse other people's libraries. I have the games showing up via search along with a show page for each game.
I am running into two problems: can't add games to a user's library and can't view other people's library.
Here is my games controller:
class GamesController < ApplicationController
#search for games
def index
#games = GiantBomb::Search.new().query(params[:query]).resources('game').limit(100).fetch
end
#Shows data for individual games
def show
#game = GiantBomb::Game.detail(params[:id])
end
#Adding and removing games to a user's library
def library
type = params[:type]
#game = GiantBomb::Game
if type == "add"
current_user.library_additions << #game
redirect_to user_library_path, notice: "Game was added to your library"
elsif type == "remove"
current_user.library_additions.delete(#game)
redirect_to root_path, notice: "Game was removed from your library"
else
# Type missing, nothing happens
redirect_to game_path(#game), notice: "Looks like nothing happened. Try once more!"
end
end
private
def game_params
params.require(:game).permit(:name, :search, :query)
end
end
When I try to add a game to my library, I get "Game(#70231217467720) expected, got GiantBomb::Game which is an instance of Class(#70231150447440)". So my #game is incorrect but I am not sure what should be there instead.
Even if I could add the game to my library, I can't view other user's libraries. Here is my current controller.
class LibraryController < ApplicationController
#before_action :authenticate_user!
def index
#library_games = User.library_additions
end
end
I get 'undefined method library_additions' even though it is in the model. If I change User to current_user I can see the page, but that means users can only see their page and not others.
Here are my game, user, and library model:
class Game < ApplicationRecord
has_many :libraries
has_many :added_games, through: :libraries, source: :user
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :games
has_many :libraries
has_many :library_additions, through: :libraries, source: :game
end
class Library < ApplicationRecord
belongs_to :game
belongs_to :user
end
I made my library a join table for users and games but I am thinking I didn't do it correctly. Here is my schema:
ActiveRecord::Schema.define(version: 2020_11_19_143536) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "games", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "search"
end
create_table "libraries", force: :cascade do |t|
t.integer "user_id"
t.integer "game_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
Am I missing a migration or do I need the rework the models and controllers?
[edit] Here are my routes, I am getting a pathing error when I try to add a game.
Rails.application.routes.draw do
devise_for :users
resources :games do
member do
put "add", to: "games#library"
put "remove", to: "games#library"
end
end
resources :library, only:[:index]
root to: 'pages#home'
get '/search', to: 'games#search', as: :search
get '/games', to: 'games#index', as: :index
get '/user/:id', to: 'user#show'
get '/user/:id/library', to: 'library#index', as: :user_library
end
Here, the error clearly states it is expecting an instance of Game not GiantBomb::Game, so you have to create one.
#game = Game.new(name: 'some name', other fields ....)
if type == "add"
current_user.library_additions << #game
About the other error you can only call association methods on an instance not on the class itself
def index
# You could get the user'id through params for example
#library_games = User.find(params[:user_id]).library_additions
end
Related
im new to Ruby on Rails. I try to connect 3 tables.
My Models:
Course.rb:
class Course < ApplicationRecord
has_many :chapters
has_many :lessons, through: :chapters
end
Chapter.rb
class Chapter < ApplicationRecord
belongs_to :course
belongs_to :lesson
end
Lesson.rb
class Lesson < ApplicationRecord
has_many :chapters
has_many :courses, through: :chapters
end
My Course Controller looks like this:
class CourseController < ApplicationController
def index
end
def show
#course = Course.find(params[:id])
#chapters = #course.chapters
#lessons = #course.lessons
end
end
and show.html.erb
<% #chapters.each do |c| %>
<%= c.chapter %>
<%= link_to "lektionen", c %>
<% end %>
I can see a list of my courses. Thats working. Also i can see the chapters. But for any reason it is not forwarding the id for making a relationship between these models.
Chapter Controller:
class ChapterController < ApplicationController
def index
end
def show
#chapters = Chapter.find(params[:id])
#lessons = #chapters.lessons
end
end
i want to forward the id of the course so that chapters << lessons are the children of courses.
-courses
-- chapters
--- lessons
My schema:
ActiveRecord::Schema.define(version: 2021_04_11_090326) do
create_table "chapters", force: :cascade do |t|
t.string "chapter"
t.integer "course_id", null: false
t.integer "lesson_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["course_id"], name: "index_chapters_on_course_id"
t.index ["lesson_id"], name: "index_chapters_on_lesson_id"
end
create_table "courses", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "lessons", force: :cascade do |t|
t.string "title"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "chapters", "courses"
add_foreign_key "chapters", "lessons"
end
My routes.rb
Rails.application.routes.draw do
get 'chapter/index'
get 'chapter/show'
get 'course/index'
get 'course/show'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :course
resources :chapter
resources :lessons
end
if i click "courses#show" action i could see the chapters. If i click on one chapter i got this error:
undefined method `lessons' for #Chapter:0x000055f7aeefbe50
I want to show only lessons which are belongs to a specific chapter and the chapter is related to the course.
But im not in able to get this working.
I hope yo can help me.
I think you should rethink you data model in this case:
Make it Course -> Chapter -> Lesson
class Course < ApplicationRecord
has_many :chapters # a course has many chapters. alright
end
class Chapter < ApplicationRecord
belongs_to :course # a chapter always belongs to a course
has_many :lessons # and has many lessons ! check!
end
class Lesson < ApplicationRecord
belongs_to :chapter # a lesson belongs to a chapter
# helper method if you want to access course directly from lesson
def course
#course ||= chapter&.course
end
end
Database
Your database should look something like this:
suggestion -> do not name it chapter in chapters table. call it title or name
ActiveRecord::Schema.define(version: 2021_04_11_090326) do
create_table "courses", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "chapters", force: :cascade do |t|
t.string "title"
t.integer "course_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["course_id"], name: "index_chapters_on_course_id"
end
create_table "lessons", force: :cascade do |t|
t.string "title"
t.integer "chapter_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["chapter_id"], name: "index_lessons_on_chapter_id"
end
end
With this setup you are ready to go with a good hierarchy.
Draw the routes:
Rails.application.routes.draw do
resources :courses
resources :chapters
resources :lessons
end
if you want to make the hierarchy in the url you could do something more advanced
Rails.application.routes.draw do
# use this if you know what you are doing :D
resources :courses do
resources :chapters do
resources :lessons
end
end
end
Your controllers:
class CourseController < ApplicationController
def index
courses
end
def show
course
end
protected
# get all courses
def courses
#courses ||= Course.order(:name)
end
# find course by id
def course
#course ||= courses.find(params[:id])
end
end
In your view views/courses/show.html.erb
<h1><%= #course.name %></h1>
<% #course.chapters.each do |chapter| %>
<h2><%= chapter.title %></h2>
<% chapter.lessons.each do |lesson| %>
<%= link_to "Lesson: " + lesson.title, lesson_url(lesson) %>
<% end %>
<% end %>
N+1 problem:
To avoid N+1 problem when rendering this hierarchy change your controller and add includes to the query:
def course
#course ||= courses.includes(chapters: :lessons).find(params[:id])
end
I hope this gives you a good start with a clean data model.
I save the #booking with a user (called "booker"). Right after the #booking.save I can retrieve #booking.booker in the command line that display all the properties from the user (email, password, id, etc.). However After leaving the create method, impossible to retrieve it (for example from the show) : #booking.booker = nil .
I guess that commes from a mistake in my booking model : I have belongs_to and has_many_through. If the error comes from here, how to solve it without having to change all the db?
booking_controller.rb
class BookingsController < ApplicationController
before_action :set_booking, only: [:show, :edit, :update ]
before_action :set_booking_format, only: [:destroy ]
def index
end
def my_bookings
#bookings = BookingPolicy::Scope.new(current_user, Booking).scope.where(booker: current_user)
end
def show
authorize #booking
end
def new
#garden = Garden.find(params[:garden_id])
#booking = Booking.new
authorize #booking
end
def create
#garden = Garden.find params[:garden_id]
#booking = #garden.bookings.build(booker: current_user)
authorize #booking
if #booking.save
redirect_to garden_booking_path(#booking, current_user)
end
end
def update
end
private
def set_booking
#booking = Booking.find(params[:id])
end
def set_booking_format
#booking = Booking.find(params[:format])
end
def booking_params
params.require(:booking).permit(:garden_id, :booker_id, :date)
end
end
booking.rb
class Booking < ApplicationRecord
belongs_to :garden
belongs_to :booker, class_name: "User"
end
garden.rb
class Garden < ApplicationRecord
belongs_to :user
has_many :bookings, dependent: :destroy
end
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :gardens
has_and_belongs_to_many :bookings
end
schema.rb
create_table "bookings", force: :cascade do |t|
t.date "date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "garden_id"
t.integer "booker_id"
t.index ["garden_id"], name: "index_bookings_on_garden_id"
end
create_table "gardens", force: :cascade do |t|
t.string "title"
t.text "details"
t.integer "surface"
t.text "address"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "availabilities"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "admin", default: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "bookings", "gardens"
end
In your model, user.rb:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :gardens
has_and_belongs_to_many :bookings
end
The :bookings association should be has_many. You aren't using a join table.
See: https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
The belongs_to part of the habtm association is looking for a foreign key, which doesn't exist. You can retrieve #booking.booker before moving to a different controller action because you aren't hitting the database at all, you're just retrieving the instance variables' association.
hey guys im working on a application where a devise user sign ups and logs in, Once the user logs in they can 'create a team' or 'join a team'. I have my associations set up like this
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
validates_presence_of :phone, :city, :state, :street, :zip, presence: true, on: :create
belongs_to :team
end
team.rb
class Team < ApplicationRecord
has_many :users
end
and my tables are set up
schema.rb
create_table "teams", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "team_name"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "firstname"
t.integer "team_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
team_controller.rb
class TeamController < ApplicationController
before_action :authenticate_user!
def index
#team = current_user.team
end
def new_team
end
def create_team
#team = current_user.create_team(sanitize_team)
if #team.save
redirect_to team_root_path
else
render json: #team.errors.full_messages
end
end
def join_team
#teams = Team.all
end
def team
end
private
def sanitize_team
params.require(:team).permit(:team_name, :team_statement)
end
end
I want the users 'team_id' attribute to update with the teams id when they create a team. or when they join a team. Are my associations correct? how would i make this happen in the controller ?
Yes, associations are correct. You can do it better only by adding foreign key to your database schema. It can be done by generator rails g migration AddTeamToUsers team:references
More information about associations can be found here: https://guides.rubyonrails.org/association_basics.html
In controller you have to change only the whitelisting params to allow team_id. And you probably need to add to your form in view something like this:
<%= f.select :team_id, Team.all.map { |t| [t.team_name, t.id] } %>
Let's strip your code example down to the minimum required:
# app/models/team.rb
class Team < ApplicationRecord
has_many :users
end
# app/models/user.rb
class User < ApplicationRecord
belongs_to :team
end
# db/migrate/20181124230131_create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.string :team_name
t.timestamps
end
end
end
# db/migrate/20181124230136_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.belongs_to :team
t.timestamps
end
end
end
Then in your controller:
team = Team.where(team_name: 'foo').first_or_create!
team.users << current_user
Start by setting the association up as optional:
class User < ApplicationController
belongs_to :team, optional: true
end
Otherwise the validations on the user model will not let the user be saved without a team.
Then setup the teams resource:
# config/routes.rb
resources :teams do
post :join
end
post :join creates an additional POST /teams/:team_id/join route.
Then setup the controller:
class TeamsController
# ...
# GET /teams/new
def new
#team = Team.find
end
# POST /teams
def create
#team = Team.new(team_params)
if #team.save
unless current_user.team
current_user.update(team: #team)
end
redirect_to 'somewhere'
else
render :new
end
end
# ...
def join
#team = Team.find(params[:team_id])
if current_user.update(team: #team)
redirect_to #team, notice: 'Team joined'
else
redirect_to #team, error: 'Could not join team'
end
end
#
private
def team_params
params.require(:team).permit(:team_name, :team_statement)
end
end
Note that prefixing your action names is neither needed nor compatible with the "Rails way". Prefixing column names is also largely superfluous.
I’m new to ruby on rails and I’m a bit stuck with what the best next step is in a multi-tenancy application I’m building.
Basically I want to scope resources by account_id, so I have created a method and helper called current_account in my accounts base_controller.
However, the tutorial I’m following scopes current_account by subdomain which I do not want to do. So I need a way to identify the current user’s account_id so that that I can have a resource variable #contact = current_account.contacts."all".
Do I need to make a new association between the user and account model so that I can use the current_user helper to define the current account id or is there a better way? If so, what is the best way to do this?
Background
The first user who signs up becomes the account owner. Account owners can then invite other users to the account. I'm using the devise gem. Resources are scoped by account so that only users linked to an account can see the records belonging to that account.
Base Controller
module Accounts
class BaseController < ApplicationController
def current_account
#current_account ||= ?????
end
helper_method :current_account
def owner?
current_account.owner == current_user
end
helper_method :owner?
end
end
Contacts (my resource) Controller
module Accounts
class ContactsController < Accounts::BaseController
def index
#contact = current_account.contacts.all
end
end
end
Account Model
class Account < ActiveRecord::Base
belongs_to :owner, class_name: "User"
accepts_nested_attributes_for :owner
validates :subdomain, presence: true, uniqueness: true
has_many :contacts
has_many :invitations
has_many :memberships
has_many :users, through: :memberships
end
Invitation Model
class Invitation < ActiveRecord::Base
belongs_to :account
validates :email, presence: true
end
Membership Model
class Membership < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
User Model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Routes
Rails.application.routes.draw do
devise_for :users
scope module: "accounts" do
resources 'dashboard'
resources 'contacts'
resources :invitations, only: [:new, :create] do
member do
get :accept
patch :accepted
end
end
resources :users, only: [:index, :destroy]
end
Schema
ActiveRecord::Schema.define(version: 20170124002015) do
create_table "accounts", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "owner_id"
t.string "subdomain"
end
add_index "accounts", ["subdomain"], name: "index_accounts_on_subdomain"
create_table "contacts", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "phone"
t.string "email"
t.text "comments"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "account_id"
end
add_index "contacts", ["account_id"], name: "index_contacts_on_account_id"
create_table "invitations", force: true do |t|
t.string "email"
t.integer "account_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "token"
end
add_index "invitations", ["account_id"], name: "index_invitations_on_account_id"
add_index "invitations", ["token"], name: "index_invitations_on_token"
create_table "memberships", force: true do |t|
t.integer "account_id"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "memberships", ["account_id"], name: "index_memberships_on_account_id"
add_index "memberships", ["user_id"], name: "index_memberships_on_user_id"
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
There are two possible associations between users and accounts:
users have many accounts
users belong to an account
In the first case, the tenant cannot be set from the current_user, because it's unclear which account should be used as the current tenant. The membership table in the schema.rb indicates this is the approach taken by the tutorial you mentioned. Loading the account by subdomain helps specify which account shall be used as the current tenant.
In the second case, every user has just one account. Users get an account_id, the membership table becomes obsolete, and you can load the current tenant like so:
def current_account
#current_account ||= current_user.account
end
Do I need to make a new association between the user and account model
so that I can use the current_user helper to define the current
account id or is there a better way? If so, what is the best way to do
this?
It seems to me that you want to take the second approach, which requires that an account has_many users and a user belongs_to an account.
My app has users and users are able to post links to my app. I have an association set up so that a user has many :links and links belong_to a user (see below for models). Now I am trying to get the users email to appear in the show template and I am getting a nil value for Link.user for new links. Can someone maybe shed some light as to why? Is my association incorrect? The user has been logged in when posting links.
User model:
class User < ActiveRecord::Base
has_many :links
acts_as_voter
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Link model:
class Link < ActiveRecord::Base
belongs_to :user
acts_as_votable
attr_accessor :avatar
mount_uploader :avatar, AvatarUploader
end
show.html.erb:
<%= time_ago_in_words(#link.created_at) %> by <%= #link.user.try(:email) %>
Schema:
create_table "links", force: :cascade do |t|
t.string "title"
t.string "url"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Link creation in Links controller;
def new
#link = Link.new
end
def create
#link = Link.new(link_params)
if #link.save
redirect_to root_path
else
render 'new'
end
end
private
def link_params
params.require(:link).permit(:title, :url, :avatar)
end
Make sure the links are actually being assigned to a user when created.
Using the rails console. Try looping through the links and making sure they have user_ids:
in the rails console:
ap Link.all.map(&:user)
If they are indeed owned by a user
<%= #link.user.email %>
should print out the email.
You should be able to do something like this in your controller:
#user.links << params[:new_link]
Make sure that much is working.
You are using devise. Inside your Link controller, you have to authenticate your logged on user.
# app/controllers/links_controller.rb
before_action :authenticate_user!
def create
#link = current_user.links.new(link_params)
if #link.save
redirect_to root_path
else
render 'new' # generally this is a render 'edit'
end
end
I truncated the file here. Please read the devise manual