So I have the following functionality where I have courses, course modules and course exercises.
I have it where users can mark off modules once completed when all modules are completed the course gets set to complete.
However, this is applying to all users, not individual users. So, for example, what is currently happening is that one user completes the course and when it's being marked as complete but if I sign in as a second user (who hasn't completed the course) it's being marked as complete.
From my research, I believe I could achieve this using a has_many_through association, but I'm unsure how to set this up.
Here is how I have things set up so far.
schema.rb
create_table "course_exercises", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "video"
t.integer "course_module_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.index ["course_module_id"], name: "index_course_exercises_on_course_module_id"
t.index ["slug"], name: "index_course_exercises_on_slug", unique: true
end
create_table "course_modules", force: :cascade do |t|
t.string "title"
t.integer "course_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.boolean "complete", default: false, null: false
t.index ["course_id"], name: "index_course_modules_on_course_id"
t.index ["slug"], name: "index_course_modules_on_slug", unique: true
end
create_table "courses", force: :cascade do |t|
t.string "title"
t.text "summary"
t.text "description"
t.string "trailer"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.boolean "complete", default: false, null: false
t.index ["slug"], name: "index_courses_on_slug", unique: true
end
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
has_one_attached :avatar
has_many :courses
def after_confirmation
welcome_email
super
end
protected
def welcome_email
UserMailer.welcome_email(self).deliver
end
end
course.rb
class Course < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :slugged
has_many :users
has_many :course_modules
validates :title, :summary, :description, :trailer, :price, presence: true
def complete!
update_attribute(:complete, true)
end
end
course_module.rb
class CourseModule < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :slugged
belongs_to :course
has_many :course_exercises
validates :title, :course_id, presence: true
scope :completed, -> { where(complete: true) }
after_save :update_course, if: :complete?
private
def update_course
course.complete! if course.course_modules.all?(&:complete?)
end
end
Completed modules
Completed course
Databases:
Course
Course Modules
But as I mentioned above, it's getting assigned to all users, not individual users.
Any help here is appreciated.
As per the description it seems like you will be needing another table to
capture the data user wise to show completed modules.
But another catch here is that you will also be needing to capture the progress
of course_exersises a particular user has completed so that after completing
all the exercises you can update the course_module.
Note: Entery in below mentioned table in done only when a user has completed the
given exercise, also we will be having the timestamp as provided by rails.
User
has_many :courses, through: :user_courses
has_many :exercises, through: :user_course_exercise
UserCourseExercise
belongs_to :user
belongs_to :course_exercise
#table columns
user_id
exercise_id
Entry in this table will be done if all the exercises of a particular course has
been completed.
UserCourse
belongs_to :user
belongs_to :course_exercise
#table columns
user_id
course_id
The approach of having two tables would be that when you need to show the exercise
data corresponing to a particular user then you will be using user_course_exercise
and when completed courses are needed then usign the user_course table
Related
I am making a basic app for the growing incel online community linking them to various pieces of literature they could find useful, helpful and guiding. I am having difficulty creating proper associations.
I have several models:
User: user.rb, a model of a user to view, comment and like books.
like: like.rb, like model to assign likes to books.
comment: comment.rb, comment model to comment on book models (via a user).
book: book.rb, model for books, will route/view pdf's to host server.
bookshares: book_share.rb, will be a join table linking users to likes to comments to books and so on and vice versa.
godmodel: hypothetical model not yet implemented to link together everything in an all encompassing manner.
So, I want users to be able to be create with a username and be able to view, like and comment books on the 'website' that will eventually be migrated over to a android app. Here is my abysmal code:
class BookShare < ApplicationRecord
validates :book_id, presence: true, uniqueness: true
validates :viewer_id, presence: true, uniqueness: true
belongs_to :user
belongs_to :book
belongs_to :comment
end
class Book < ApplicationRecord
validates :title, presence: true
validates :author, presence: true
validates :user_id, presence: true
validates :isbn, presence: true
validates :title, presence: true
has_many :book_shares
has_many :users, through: :book_shares
has_many :likes, through: :book_shares
has_many :comments, through: :book_shares
end
class Comment < ApplicationRecord
validates :user_id, presence: true, uniqueness: true
validates :book_id, presence: true, uniqueness: true
validates :title, presence: true
has_many :book_shares
has_many :books, through: :book_shares
end
class GodModel < ApplicationRecord
has_many :books
has_many :users
has_many :comments
#has_many :likes
has_many :topics
has_many :book_shares
end
class Like < ApplicationRecord
# not fully implemented yet.
validates :user_id, presence: true
belongs_to :user
end
class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
has_many :book_shares
has_many :comments, through: :book_shares
has_many :books, through: :book_shares
end
class Topic < ApplicationRecord
# not implemented yet
end
Here is my schema:
ActiveRecord::Schema[7.0].define(version: 2022_04_12_145402) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "book_shares", force: :cascade do |t|
t.integer "book_id", null: false
t.integer "viewer_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["book_id", "viewer_id"], name: "index_book_shares_on_book_id_and_viewer_id", unique: true
t.index ["book_id"], name: "index_book_shares_on_book_id"
t.index ["viewer_id"], name: "index_book_shares_on_viewer_id"
end
create_table "books", force: :cascade do |t|
t.string "title", null: false
t.string "author", null: false
t.integer "user_id", null: false
t.text "body_info"
t.integer "isbn", null: false
t.binary "photo"
t.binary "r_data"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_books_on_user_id"
end
create_table "comments", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "book_id", null: false
t.text "body_txt"
t.string "title"
t.binary "photo"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["book_id"], name: "index_comments_on_book_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
create_table "god_models", force: :cascade do |t|
t.string "title"
t.integer "user_id"
t.integer "comment_id"
t.integer "book_share_id"
t.integer "book_id"
t.integer "like"
t.integer "topic"
t.binary "data_x"
t.date "today"
t.binary "title2"
t.boolean "nullfy"
t.float "nums"
t.text "body"
t.datetime "create_at_"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["body"], name: "index_god_models_on_body"
t.index ["book_id"], name: "index_god_models_on_book_id"
t.index ["book_share_id"], name: "index_god_models_on_book_share_id"
t.index ["comment_id"], name: "index_god_models_on_comment_id"
t.index ["data_x"], name: "index_god_models_on_data_x"
t.index ["like"], name: "index_god_models_on_like"
t.index ["nullfy"], name: "index_god_models_on_nullfy"
t.index ["nums"], name: "index_god_models_on_nums"
t.index ["title2"], name: "index_god_models_on_title2"
t.index ["today"], name: "index_god_models_on_today"
t.index ["topic"], name: "index_god_models_on_topic"
t.index ["user_id"], name: "index_god_models_on_user_id"
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.string "likeable_type"
t.bigint "likeable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["likeable_type", "likeable_id"], name: "index_likes_on_likeable"
t.index ["user_id", "likeable_type", "likeable_id"], name: "index_likes_on_user_id_and_likeable_type_and_likeable_id", unique: true
end
create_table "users", force: :cascade do |t|
t.string "username", null: false
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["username"], name: "index_users_on_username", unique: true
end
end
Example controller:
class UsersController < ApplicationController
def index
render plain: 'index'
end
def show
render plain: 'show'
end
def new
render plain: 'new'
end
def destroy
render plain: 'destroy'
end
def update
redner update: 'update'
end
private
def usershare_param
params.require(:user).permit(:username)
end
end
This is what I produced so far. I am able to create the model object, save them I think and populate their fields but I don't think my models are working with the given associations.
I tried using erd but it does not work. Given the use of my app is the models/associations correctly made? I want to have a user who can view/comment/like books of interest. A book can have many likes and comments, books can be viewed by many users, users can like and comment many books, topics will be implemented later to assort the books. The entire mechanism of liking/commenting/viewing via a user(s) will be implemented via a joins table called bookshares. I want to write my associations correctly before moving onto the view/routes part of the mini-protect.
With 4 tables, books, users, comments and likes. You can implement the given design.
a book can have many likers(users), a user can like many books, forms many-to-many relationship between users and books. Make likes a join table.
Similarly, a book has many comments, a user can write many comments. Make comments a join table between users and books with extra fields specific to comment.
# app/models/book.rb
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :lovers, through: :likes, source: :user
has_many :commentors, through: :comments, source: :user
# app/models/user.rb
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :fav_books, through: :likes, source: :book
# app/models/like.rb
belongs_to :book
belongs_to :user
# app/models/comment.rb
belongs_to :book
belongs_to :user
You can consult this guide to explore topics in more depth.
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :events, foreign_key: :creator_id, class_name: "Event"
end
class Event < ApplicationRecord
belongs_to :creator, class_name: "User", foreign_key: :creator_id
end
When I go to the events/new route I get this error: unknown attribute 'creator_id' for Event. I have done the database migration, I have added before_action's.
def new
#event = current_user.events.build(event_params)
end
Any ideas?
Update:
Status Migration ID Migration Name
--------------------------------------------------
up 20210610064055 Create events
up 20210610070312 Devise create users
up 20210612072338 Add body title to event
up 20210612091329 Add foreign key
**Further updates as requested in the comment section. **
Migrations file:
class AddForeignKey < ActiveRecord::Migration[6.1]
def change
add_column :users, :creator_id, :integer
end
end
Database schema file:
create_table "events", force: :cascade do |t|
t.date "date"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "title"
t.text "body"
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.integer "creator_id"
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
Do I need to add a foreign key to the events table as well? I can see I have not done that which I believe is wrong...
A foreign key should be added to the table you're making a reference from. In this case you're making a reference from events to users. So creator_id would have to be added to events. And it references a specific users.id value. So you need to remove they foreign key from users and add it to events instead.
class AddForeignKey < ActiveRecord::Migration[6.1]
def change
add_column :events, :creator_id, :integer
end
end
Also, no need to specify the foreign key in the rails model specifically. The rails guides state:
By convention, Rails guesses that the column used to hold the foreign
key on this model is the name of the association with the suffix _id
added. The :foreign_key option lets you set the name of the foreign
key directly:
So
class Event < ApplicationRecord
belongs_to :creator, class_name: "User"
end
Would suffice
I need to validate email with scope but with devise it is not working. Even after modifying index for email uniqueness it is not allowing user to create on basis of scope.
i have tried adding following line on config/initializers/devise.rb
config.authentication_keys=[:email, :organization_id]
But it doesnot work.
Also i have tried with validation on model:
validates_uniqueness_of :email, scope: :organization_id
But it doesnot work.
Also tried by modifying user migration:
def up
remove_index :users, name: 'index_users_on_email'
add_index :users, [:email, :organization_id], unique: true
end
But it doesnot work as well.
Relation between user model an organization:
app/models/organization.rb
Class Organization < ApplicationRecord
has_many :users
end
app/models/user.rb
class User < ApplicationRecord
belongs_to :organization
end
Here is schema :
create_table "users", force: :cascade do |t|
t.string "type"
t.string "full_name"
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 "authentication_token", limit: 30
t.integer "organization_id"
t.string "archive_number"
t.datetime "archived_at"
t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true
t.index ["email" , "organization_id"], name: "index_users_on_email_and_organization_id", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
My problem was that:
I have user with email in organizatin 1 now is have to add another user in organization 2 with same email. While doing this i am getting error
ActiveRecord::RecordInvalid: Validation failed: Email has already been
taken
I belive that i should be able to add user with same email after adding scope under validation.
For the email unique validation, you can try this:
Define following in your model:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates_uniqueness_of :email, scope: :organization
def will_save_change_to_email?
false
end
def email_changed?
false
end
end
This worked for me.
I'm new to development, and have spent the last 12 hours (literally) trying to figure out this error message - I'm giving up for the night, but not before a quick cry for help to stackoverflow.
I have this form:
<h2>Select from the language options below (or, <%= button_to "Login", 'users/login', method: :get %></h2>
<%= form_for #language_role do |f| %>
<div id="input">
<h3>I want to learn:</h3><%= select_tag(:language_id, options_from_collection_for_select(Language.all, :id, :lang_name)) %>
</div>
<div>
<p><%= f.submit "Start learning" %></p>
</div>
<% end %>
which is giving me this error message, highlighting the line #language_role = current_user.language_roles.build : "undefined method `language_roles' for nil:NilClass"
I have three tables:
create_table "language_roles", force: :cascade do |t|
t.integer "language_id"
t.boolean "is_active"
t.boolean "is_teacher"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["user_id"], name: "index_language_roles_on_user_id"
end
create_table "languages", force: :cascade do |t|
t.string "lang_name"
t.datetime "created_at", null: false
t.datetime "updated_at", 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.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
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
The language_roles table is meant to allow a user to have many languages, as well as many roles within that language. Here are my class definitions:
class LanguageRole < ApplicationRecord
belongs_to :languages
belongs_to :users
end
class Language < ApplicationRecord
has_many :language_roles
has_many :users, :through => :language_roles
end
class User < ApplicationRecord
has_many :language_roles
has_many :languages, :through => :language_roles
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
My root path goes to 'home#index', where the user is supposed to pick a language if current_user.language_roles is empty. As such, I put this code in my home controller and language_roles controller:
class HomeController < ApplicationController
def index
#language_role = current_user.language_roles.build
end
end
class LanguageRolesController < ApplicationController
def create
#language_role = current_user.language_roles.build(language_role_params)
if #language_role.save
redirect_to root_path
else
redirect_to :back
end
end
private
def language_role_params
params.permit(:language_id)
end
end
What in the hell is the problem?? I assume I need to instantiate the variable somehow, but I'm not sure how.
Thanks,
Michael
There is a typo in your LanguageRole Model:
LanguageRole < ApplicationRecord
belongs_to :languages
belongs_to :users
end
should be
LanguageRole < ApplicationRecord
belongs_to :language
belongs_to :user
end
belongs_to associations must use the singular term.
The name of the other model is pluralized when declaring a has_many association.
Ref: http://guides.rubyonrails.org/association_basics.html
Your current_user is not defines it seems. You can install a gem called 'pry-rails' and debug your way out of this situation and any other in future. Here's a tutorial how to use it Railscasts #280
In your LanguageRole model you defined like belongs_to :users. But it should be belongs_to :user.
Your model look like ...
LanguageRole < ApplicationRecord
belongs_to :languages
belongs_to :users
end
Which should be something like
LanguageRole < ApplicationRecord
belongs_to :language
belongs_to :user
end
I am trying to create a view for photo uploads for users. I'm new to rails so I'm not quite sure if I am doing this correctly because I'm not quite sure how all the pieces fit. I am using devise and also carrierwave for user authentication and image storage in database. I'm not quite sure what to do with the params for IncomePicture_params. I want to create a view that will allow me to call and display the images and the texts for pictures of the user
I am using rails 4
Models:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:rememberable, :validatable
validates_presence_of :username
has_many :expense_pictures
has_many :income_pictures
end
class IncomePicture < ActiveRecord::Base
belongs_to :user
mount_uploader :image, ImageUploader
has_one :income_text
accepts_nested_attributes_for :income_text
end
class IncomeText < ActiveRecord::Base
belongs_to :income_picture
end
controller:
class UserController < ApplicationController
def create
User.create(user_params)
end
private
def user_params
# required input for params
# permit - returns a version of the params hash with ony the permitted attributes
params.require(:user).permit(:name, :email, :password, :password_confirmation, )
end
end
class IncomePicturesController < ApplicationController
def new
#income_picture = IncomePicture.new(IncomePicture_params)
end
def create
end
def destroy
end
private
def IncomePicture_params
params.require(:income_picture).permit(:image, income_text_attributes: [:amount])
end
end
schema
ActiveRecord::Schema.define(version: 20140723044409) do
create_table "income_pictures", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.string "image"
t.integer "user_id"
end
add_index "income_pictures", ["user_id"], name: "index_income_pictures_on_user_id"
create_table "income_texts", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.integer "income_picture_id"
t.string "amount"
end
add_index "income_texts", ["income_picture_id"], name: "index_income_texts_on_income_picture_id"
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.datetime "remember_created_at"
t.datetime "created_at"
t.datetime "updated_at"
t.string "username"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
end