I have been told to move a method "Top" from Controller to Model, but when I try to call it, it doesn't work anymore.
I am using Rails 6
This is my Controller:
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#posts = #user.posts.ordered_by_most_recent
end
def edit
#user = User.find(params[:id])
end
def following
#title = 'Following'
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = 'Followers'
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
def top
#userst = User.joins(:followers).order('COUNT(followings.follower_id) DESC').group('users.id').limit(10)
end
end
and this would be my Model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, authentication_keys: [:username]
validates :fullname, presence: true, length: { maximum: 20 }
validates :username, presence: true, length: { maximum: 20 }
validates_uniqueness_of :username
has_many :posts
has_many :active_followings, class_name: 'Following',
foreign_key: 'follower_id',
dependent: :destroy
has_many :passive_followings, class_name: 'Following',
foreign_key: 'followed_id',
dependent: :destroy
has_many :following, through: :active_followings, source: :followed
has_many :followers, through: :passive_followings, source: :follower
mount_uploader :photo, FileUploader
mount_uploader :coverimage, FileUploader
# Follows a user.
def follow(other_user)
following << other_user
end
# Unfollows a user.
def unfollow(other_user)
following.delete(other_user)
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
end
All code here makes sense to me, so I only had to create a file called top.html.erb like this to render the Top:
<article class="timeline new-initial">
<h3>Top:</h3>
<ul class="posts">
<%= render #userst %>
</ul>
</article>
Now, to be honest, I am lost, I am not sure how to move this method to the User model in the right way to read it in the view section.
This seems like a job for a scope.
Model:
scope :top, -> { joins(:followers).order('COUNT(followings.follower_id) DESC').group('users.id').limit(10) }
Controller:
def top
#userst = User.top
end
Related
I have an application with users using devise for authentication, in the user model I have added in the database a column called admin with false value by default. that way I have managed to have access as administrator to certain parts of the application.
I have a subscription model and each user when authenticated gets a free value by default. what I want to achieve is that the admin user in your user list can be able to switch from free to premium. this is the code i have and i can't get it to work.
Users Model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
#Validaciones
validates :nombre, :apellido, presence: true
devise :database_authenticatable, :validatable, password_length: 8..128
#Relaciones
has_many :patients, dependent: :destroy
has_many :articles, dependent: :destroy
has_one :profile, dependent: :destroy
has_one :suscription, dependent: :destroy
#Creación de perfil
after_create :set_profile
def set_profile
self.profile = Profile.create()
end
#Creación de suscripcion
after_create :set_suscription
def set_suscription
self.suscription = Suscription.create()
end
end
Suscription Model:
class Suscription < ApplicationRecord
belongs_to :user
enum status: {
free: 0,
premium: 1
}
end
Users controllers:
class UsersController < ApplicationController
def index
#pagy, #users = pagy(User.order(created_at: :asc), items:12)
end
def show
#user = User.find(params[:id])
end
end
Suscriptios controller:
class SuscriptionsController < ApplicationController
before_action :set_suscription
def show
end
def edit
end
def update
#suscription = Suscription.find(params[:id]).update_params
redirect_to profile_path
flash[:notice] = "La suscripción ha sido actualizada"
end
private
def set_suscription
#suscription = (current_user.suscription ||= Suscription.create)
end
def suscription_params
params.require(:suscription).permit(:status)
end
end
Route:
#UPDATE PREMIUM
patch "suscriptions", to:"suscriptions#update", as: "user_premium"
View (Link):
<%= link_to 'Update', user_premium_path ,method: :patch %>
This should fix it:
subscriptions_controller.rb
def update
#suscription = Suscription.find(params[:id]).update(subscription_params)
redirect_to profile_path
flash[:notice] = "La suscripción ha sido actualizada"
end
view
<%= link_to 'Update', user_premium_path(id: #subscription.id, status: "premium"), method: :patch %>
One other thing that is not needed, but normally I would see something like this in a controller:
private
def set_suscription
#suscription = Suscription.find(params[:id])
end
which then makes your update method look like this:
def update
#subscription.update(subscription_params)
redirect_to profile_path
flash[:notice] = "La suscripción ha sido actualizada"
end
This is all assuming you are simply trying to update the subscription from free to premium with your link_to. I wouldn't recommend doing anything like this, because what if someone accidentally marks this? They can no longer go back to a free subscription. Maybe have a modal open that is routed to subscription edit with a drop down to select the status would be better?
I have problems with my Rails Block. After I implemented a comment-section I am not able to create posts anymore. The console gives me a rollback transaction. So I did
p = Post.new
p.valid? # false
p.errors.messages
It seems I have some validation problems with user :user=>["must exist"]. But before I implemented comments it did work. Can someone help me out?
User.rb
class User < ApplicationRecord
has_many :posts
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
validates :title, presence: true, length: {minimum: 5}
validates :body, presence: true
has_attached_file :image #, :styles => { :medium => "300x300>", :thumb => "100x100>" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
Post-migrate
class CreatePosts < ActiveRecord::Migration[5.1]
def change
create_table :posts do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
Post_controller
class PostsController < ApplicationController
def index
#posts = Post.all.order("created_at DESC")
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :body, :theme)
end
end
When you are creating a post you need to assign a user to that post in the create method under your posts controller. You could try something like this.
def create
if current_user
#post.user_id = current_user.id
end
## More create method stuff
end
By default, in a belongs_to association a user is required to create the post otherwise you will not be able to create the post. Since, from the looks of it, you do not have anything that assigns the user to that post in the create method.
I have to build a simple app that allows users to loan and borrow books. Simply put a User can create books, and they can pick another user to loan the book to.
I have three models User, Book and Loan:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :books
has_many :loans, through: :books
has_many :borrowings, class_name: "Loan"
validates :username, uniqueness: true
validates :username, presence: true
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :loans
validates :title, :author, presence: true
end
class Loan < ActiveRecord::Base
belongs_to :user
belongs_to :book
validates :user, :book, :status, presence: true
end
The LoansController looks like this:
class LoansController < ApplicationController
before_action :find_book, only: [:new, :create]
def new
#users = User.all
#loan = Loan.new
authorize #loan
end
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
private
def loan_params
params.require(:loan).permit(:user_id)
end
def find_book
#book = Book.find(params[:book_id])
end
end
My form looks like:
<%= simple_form_for([#book, #loan]) do |f| %>
<%= f.input :user_id, collection: #users.map { |user| [user.username, user.id] }, prompt: "Select a User" %>
<%= f.submit %>
<% end %>
If I submit the form without selecting a user, and keep the "Select a User" prompt option, the form is submitted and the app crash because it can't find a user with id=
I don't know why the user presence validation in the form does not work...
you will change your Create method
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find_by_id(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
I'm trying to allow users to create projects...and as soon as a user creates a project...they will automatically be following that project. (I have my app setup to allow a user to follow a project from a 'follow' button on the project profile). I would like the project creator to automatically be following the new project without having to click the 'follow' button. I rearranged my code as per Bilal's answer...but now clicking 'create project' simply refreshes the 'new' view (no project gets posted). I assumed this has to do with the Pundit authorizations but perhaps someone can clarify why the 'create' action is no longer working...
My Projects Model:
class Project < ActiveRecord::Base
belongs_to :owner, :foreign_key=>'user_id', :class_name=>'User'
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
validates :title, presence: true
validates :background, presence: true
validates :projectimage, presence: true
mount_uploader :projectimage, ProjectimageUploader
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
after_update :crop_projectimage
def crop_projectimage
projectimage.recreate_versions! if crop_x.present?
end
def private?
self.is_private == true
end
def public?
self.is_private == false
end
end
Relationships Model:
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "Project"
validates :follower_id, presence: true
validates :followed_id, presence: true
enum role: [:admin, :collaborator, :visitor]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :visitor
end
end
My Projects Controller:
class ProjectsController < ApplicationController
before_filter :authenticate_user!, only: [:create, :new, :edit, :update, :delete, :followers]
# CREATES REDIRECT & ALERT MESSAGE WHEN PUNDIT SEES SOMEONE IS NOT AUTHORIZED (via :not_authorized_in_project below)
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def new
#project = Project.new
end
def show
#project = Project.find(params[:id])
authorize #project, :visit?
# #user = User.where(:id => #project.user_id).first
rescue Pundit::NotAuthorizedError
flash[:warning] = "You are not authorized to access this page."
redirect_to project_path || root_path
end
def index
#projects = policy_scope(Project).all
end
def create
#project = current_user.own_projects.build(project_params)
#project.followers << current_user
if #project.save
if params[:project][:projectimage].present?
render :crop
else
flash[:success] = "You've successfully created a Project..."
redirect_to #project
end
else
render 'new'
end
end
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
if params[:project][:projectimage].present?
render :crop
else
flash[:success] = "Project Created"
redirect_to #project
end
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Project destroyed"
redirect_to users_path
end
def followers
#title = "Following this Project"
#project = Project.find(params[:id])
#project = #project.followers.paginate(page: params[:page])
render 'show_follow_project'
end
private
def project_params
params.require(:project).permit(:title, :background, :is_private, :projectimage, :user_id, :crop_x, :crop_y, :crop_w, :crop_h)
end
def user_not_authorized
flash[:warning] = "You are not authorized to access this page."
redirect_to project_path(#project) || root_path
end
end
My User Model:
class User < ActiveRecord::Base
has_many :own_projects, :class_name=>'Project'
has_many :projects
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_projects, through: :relationships, source: :followed
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def following?(some_project)
relationships.find_by_followed_id(some_project.id)
end
def follow!(some_project)
self.relationships.create!(followed_id: some_project.id)
end
def unfollow!(some_project)
relationships.find_by_followed_id(some_project.id).destroy
end
Pundit Project Policy:
class ProjectPolicy < Struct.new(:user, :project)
class Scope < Struct.new(:user, :scope)
# SCOPE & RESOLVE METHOD USED TO RESTRICT PROJECTS INDEX TO PUBLIC & THOSE YOU'RE AN ADMIN/COLLAB ON
def resolve
followed_project_ids = user.followed_projects.map(&:id)
public_project_ids = Project.where(:is_private=>false).map(&:id)
Project.where(:id=>followed_project_ids + public_project_ids)
end
end
def update?
user.project_admin? || user.project_collaborator?
end
# METHOD USED IN PROJECTS_CONTROLLER (SHOW) TO RESTRICT VISITING PRIVATE PROJECT PROFILES TO ADMINS & COLLABS
def visit?
user.project_admin?(project) || user.project_collaborator?(project)
end
end
It's never a good idea to use current_user in a model, see this for reference.
Any easy and efficient place to set this thing would be the controller itself. So, you can write the following code:
def create
#project = current_user.own_projects.build(project_params)
#project.followers << current_user
if #project.save
if params[:project][:projectimage].present?
render :crop
else
flash[:success] = "You've successfully created a Project..."
redirect_to #project
end
else
render 'new'
end
end
The database column for my users' names is first_name. Score is not a column in the guides database.
In my karma controller:
def hiscores
#users = User.all.map(&:guides).flatten.map(&:score).sort
end
In hiscores.html.erb
<h1>Karma Hiscores</h1>
<blockquote>Users sorted by most karma</blockquote>
<ul>
<% #users.each do |user| %>
<li><%= user %></li>
<% end %>
</ul>
EDITS
Guide model
class Guide < ActiveRecord::Base
validates :link, uniqueness: true
validates :link, presence: true
validates :title, presence: true
belongs_to :user
has_many :comments
acts_as_taggable
acts_as_votable
def to_param
"#{id} #{title}".parameterize
end
def score
upvotes.count - downvotes.count
end
end
User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, #:recoverable,
:rememberable, :trackable, :validatable
validates :first_name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
validates :runescape_username, presence: true
has_many :guides
has_many :comments
acts_as_voter
end
Here is what my karma controller looks like now:
class KarmaController < ApplicationController
def hiscores
#users = (guides.upvote.count - guides.downvote.count).desc
end
end
In your karma controller:
def hiscores
#users = User.all.sort{ |x, y| y.user_score <=> x.user_score }
end
In your User.rb:
If each user has_many: guides, then
def user_score
self.guides.inject(0) { |sum, guide| sum += guide.score }
end
Else if each user has_one: guide, then
def user_score
self.guide.score
end
How about something like this:
#users = User.find(:all, include: [:guides], order: "(guides.upvotes.count - guides.downvotes.count) desc")
I am not sure if include is required, I am typing from the top of my head :)
Edit:
You should try to use Caching from acts_as_votable.
This would give you fields such as cached_votes_up and cached_votes_down and cached_votes_score.
Then you could query users as
#users = User.find(:all, include: [:guides], order: "guides.cached_votes_score desc")