Validates uniqueness record per user per lesson // Rails 4 - ruby-on-rails

Problem: I want to have only 1 UserLesson per User per Video
because I'm building a tracking system (progress system) User shall be able to see how many lessons are remaining lesson/total lesson and also when marked as completed shall add css
I send the data from the view to the controller:
<%= link_to 'Mark as completed', user_lessons_path(#user_lesson, user_lesson: {user_id: current_user.id, lesson_id: #lesson.id}), :method => :post, class: 'btn btn-primary-big' %>
The controller receives data and launch the create method
class UserLessonsController < ApplicationController
before_filter :set_user_and_lesson
def show
end
def create
#user_lesson = UserLesson.create(user_lesson_params)
if #user_lesson.save
flash[:success] = "You rock! Keep up ;)"
redirect_to(:back)
else
flash[:success] = "You have already completed this lesson"
redirect_to(:back)
end
end
private
def user_lesson_params
params.require(:user_lesson).permit(:user_id, :lesson_id, :completed)
end
end
Here is the model relationship
class UserLesson < ActiveRecord::Base
belongs_to :user
belongs_to :lesson
# validates_uniqueness_of :user_lesson, :scope => [:user, :lesson]
end
class Lesson < ActiveRecord::Base
has_one :lecture, through: :chapter
belongs_to :chapter
end
class User < ActiveRecord::Base
has_many :enrollments
has_many :user_lessons
has_many :lectures, through: :enrollments
accepts_nested_attributes_for :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :user
belongs_to :lecture
validates :lecture, uniqueness: { scope: :user, message: "should happen once per user" }
end
class Lecture < ActiveRecord::Base
belongs_to :category
has_many :lessons, through: :chapters, dependent: :destroy
has_many :chapters
belongs_to :teacher
# For course user relationship
has_many :enrollments
has_many :users, through: :enrollments
accepts_nested_attributes_for :enrollments
accepts_nested_attributes_for :chapters
end
class Chapter < ActiveRecord::Base
has_many :lessons
belongs_to :lecture
accepts_nested_attributes_for :lessons
end
My guess was to validates uniqueness of user-lesson per user and lesson. However can't seem to work got the error message.

Add this validation to your model to ensure uniqueness by user scope:
validates :lesson_id, :uniqueness => {:scope=>:user_id}

Related

About rails polymorphic

I have three models , Article, User, Approval. and this is the Approval table
def change
create_table :approvals do |t|
t.references :user, foreign_key: true
t.references :approvable, polymorphic: true
t.timestamps
end
end
this is the three models
class User < ApplicationRecord
has_many :approvals
has_many :articles, dependent: :destroy
end
class Article < ApplicationRecord
belongs_to :user
has_many :approvals, as: :approvable
end
class Approval < ApplicationRecord
belongs_to :user
belongs_to :approvable, polymorphic: true
end
now i want to show an approval image in the article show page, and it's diffenrence according if the current_user has approved the article, i think there is a very eazy way to do this, but i can only come up with a stupid way to
do this, like :
<% #article.approvals.each do |approval| %>
<% if approval.user_id == current_user.id %>
# show has approvaled image
<% end %>
<% end %>
i think it maybe inefficient, please tell me a better solution, Thanks! : )
maybe i didn't express very well, i mean the approval is praise or like?
You can first check for approvals which belongs to current_user and article.
So in controller method use(considering you have only one article at a time),
#current_user_approvals = Approval.where(user_id: current_user.id, article_id: #article.id)
and then use #current_user_approvals in view as,
<% #current_user_approvals.each do |approval| %>
# show has approval image
<% end %>
Polymorphic should not be used in this case. you can use has_many :through to achieve approval. read the docs here
the migration would be:
class CreateApprovals < ActiveRecord::Migration[5.0]
def change
create_table :approvals do |t|
t.belongs_to :user
t.belongs_to :article
end
end
end
models would be like:
class User < ApplicationRecord
has_many :articles, dependent: :destroy
has_many :approvals
has_many :approved_articles, through: :approvals, source: :article
end
class Article < ApplicationRecord
belongs_to :user
has_many :approvals
has_many :approved_users, through: :approvals, source: :user
end
class Approval < ApplicationRecord
belongs_to :user
belongs_to :article
end
Now we can do the tricks (in rails console):
# create a user
user = User.create(name: 'user')
# create an article
article = user.articles.create(name: 'article')
# check whether article is approved
user.approved_articles.include?(article) # => false
# approve the article
user.approved_articles << article
# check check whether article is approved again
user.approved_articles.include?(article) # => true
edited version, since we do want to use polymorphic on approvals. the key is to use source_type to specify the polymorphic type (in this case it's 'Article')
class User < ApplicationRecord
has_many :articles, dependent: :destroy
has_many :approvals
has_many :approved_articles, through: :approvals, source: :approvable, source_type: 'Article'
end
class Article < ApplicationRecord
belongs_to :user
has_many :approvals, as: :approvable
has_many :approved_users, through: :approvals, source: :user
end
class Approval < ApplicationRecord
belongs_to :user
belongs_to :approvable, polymorphic: true
end
tests above in console still work.

How to create new record via has_many through by Rails?

I am currently struggling with a has_many :through association in my project.
This is my model
class Group < ActiveRecord::Base
has_many :user_groups ,dependent: :destroy
has_many :users , through: :user_groups
end
class User < ActiveRecord::Base
has_many :user_groups ,dependent: :destroy
has_many :groups , through: :user_groups
end
class UserGroup < ActiveRecord::Base
belongs_to :user , inverse_of: :placements
belongs_to :group , inverse_of: :placements
validates :level , presence: true
end
So when i tried to create new group but it didn't work out.
This is my controller
class GroupController < ApplicationController
def create
group = Group.new(group_params)
group.users << User.find_by(id: current_user.id)
if group.save
render json: group, status: 201, location: [group]
else
render json: { errors: group.errors }, status: 422
end
end
private
def group_params
params.require(:group).permit(:name, :shuttle_price, :court_price)
end
end
But when i call create method i got this error.
Could not find the inverse association for group (:placements in Group)
On this line
group.users << User.find_by(id: 6)
So how can i fix this?
Thanks!
Remove :inverse_of
class UserGroup < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :level , presence: true
end
You don't need to add inverse_of there. read this when to use inverse_of

Rails - Restrict users from following themselves

I have a following system and I would like to restrict the users controller action 'follow' if the params[:id] is the same as the current user.
I use cancancan (an up to date cancan gem) to do my authorizations work.
controllers/users_controller.rb
def follow
Followership.create(leader_id: params[:id], follower_id: current_user.id)
...
end
models/user.rb
class User < ActiveRecord::Base
has_many :followers, :class_name => 'Followership', dependent: :destroy
has_many :followed_by, :class_name => 'Followership', dependent: :destroy
...
end
models/followership.rb
class Followership < ActiveRecord::Base
belongs_to :leader, :class_name => 'User'
belongs_to :follower, :class_name => 'User'
...
end
Add a validation on your Followship model:
class Followership < ActiveRecord::Base
belongs_to :leader, :class_name => 'User'
belongs_to :follower, :class_name => 'User'
validate :doesnt_follow_self
private
def doesnt_follow_self
errors.add(:base, 'You can\'t follow yourself') if leader == follower
end
end
Perhaps you can use a validation:
#app/models/followership.rb
Class FollowerShip < ActiveRecord::Base
include ActiveModel::Validations
...
validates_with FollowValidator
end
#app/validators/follow_validator.rb
class FollowValidator < ActiveModel::Validator
def validate(record)
if record.leader_id == record.follower_id
record.errors[:leader_id] << "Sorry, you can't follow yourself!"
end
end
end
I was half-way through writing this when #BroiStatse posted

Dependent destroy does not destroy dependencies

I have the following models:
class Article < ActiveRecord::Base
has_many :comments, :as => :subject, :dependent => :destroy
has_many :deleted_comments, :as => :subject, :dependent => :destroy
end
class DeletedComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
end
class Comment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end
In my database, I have quite a few DeletedComment objects where the subject is nil. The DeletedComment (and Comment) model stores :article_id, and for the ones where the subject is nil, Article.find(deleted_comment.article_id) raises an ActiveRecord::RecordNotFound error.
Are there any cases where the :dependent => :destroy would destroy the parent record but leave the dependencies untouched?
Is it possible that in some cases when I delete an Article the deleted_comments are destroyed before comments? and when the comments are destroyed, deleted_comments are created and not destroyed (because ActiveRecord has already checked the dependent deleted_comment and tried to destroy any dependencies)?
According to official documentation:
Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order for the associations to work as expected, ensure that you store the base model for the STI models in the type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts and member posts that use the posts table for STI. In this case, there must be a type column in the posts table.
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
I guess you could use examle and do something like:
class Article < ActiveRecord::Base
# for deletion only
has_many :abstract_comments, :as => :subject, :dependent => :destroy
# for 'manual' access/edition
has_many :comments, :as => :subject
has_many :deleted_comments, :as => :subject
end
class AbstractComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class DeletedComment < AbstractComment
end
class Comment < AbstractComment
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end

Validate that an object has one or more associated objects

I need to ensure that when a product is created it has atleast one category.
I could do this with a custom validation class, but I was hoping there was a more standard way of doing it.
class Product < ActiveRecord::Base
has_many :product_categories
has_many :categories, :through => :product_categories #must have at least 1
end
class Category < ActiveRecord::Base
has_many :product_categories
has_many :products, :through => :product_categories
end
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
There is a validation that will check the length of your association. Try this:
class Product < ActiveRecord::Base
has_many :product_categories
has_many :categories, :through => :product_categories
validates :categories, :length => { :minimum => 1 }
end
Ensures it has at least one category:
class Product < ActiveRecord::Base
has_many :product_categories
has_many :categories, :through => :product_categories
validates :categories, :presence => true
end
I find the error message using :presence is clearer than using length minimum 1 validation
Instead of wpgreenway's solution, I would suggest to use a hook method as before_save and use a has_and_belongs_to_many association.
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
before_save :ensure_that_a_product_belongs_to_one_category
def ensure_that_a_product_belongs_to_one_category
if self.category_ids < 1
errors.add(:base, "A product must belongs to one category at least")
return false
else
return true
end
end
class ProductsController < ApplicationController
def create
params[:category] ||= []
#product.category_ids = params[:category]
.....
end
end
And in your view, use can use for example options_from_collection_for_select

Resources