I am using CarrierWave and as of now the gallery is open to the public with no ownership. I want to setup so that for one, the user does not have to create a Gallery. The only option should be to upload photos to their account and I want to limit each user photo uploads to 5 maximum. So if User 16 signs in, they have option to upload up to 5 photos to their profile. Once that limit is reach, if the user tries to upload more it should say "Maximum photos uploaded, delete to upload more". I'm not sure exactly how to pull this off.
photo.rb model:
class Photo < ActiveRecord::Base
attr_accessible :title, :body, :gallery_id, :name, :image, :remote_image_url
has_many :user, :through => :gallery
has_many :gallery
mount_uploader :image, ImageUploader
LIMIT = 5
validate do |record|
record.validate_photo_quota
end
def validate_photo_quota
return unless self.user
if self.gallery.user(:reload).count >= LIMIT
errors.add(:base, :exceeded_quota)
end
end
end
photo controller:
class PhotosController < ApplicationController
def new
#photo = Photo.new(:gallery_id => params[:gallery_id])
end
def create
#photo = Photo.new(params[:photo])
if #photo.save
flash[:notice] = "Successfully created photos."
redirect_to #photo.gallery
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(params[:id])
end
def update
#photo = Photo.find(params[:id])
if #photo.update_attributes(paramas[:photo])
flash[:notice] = "Successfully updated photo."
redirect_to #photo.gallery
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to #photo.gallery
end
end
galleries controller:
class GalleriesController < ApplicationController
def index
#galleries = Gallery.all
end
def show
#gallery = Gallery.find(params[:id])
end
def new
#gallery = Gallery.new
end
def create
#gallery = Gallery.new(params[:gallery])
if #gallery.save
flash[:notice] = "Successfully created gallery."
redirect_to #gallery
else
render :action => 'new'
end
end
def edit
#gallery = Gallery.find(params[:id])
end
def update
#gallery = Gallery.find(params[:id])
if #gallery.update_attributes(params[:gallery])
flash[:notice] = "Successfully updated gallery."
redirect_to gallery_url
else
render :action => 'edit'
end
end
def destroy
#gallery = Gallery.find(params[:id])
#gallery.destroy
flash[:notice] = "Successfully destroy gallery."
redirect_to galleries_url
end
end
Restricting user access
To restrict user access to certain models I would use something like CanCan.
It would let you do stuff like this:
## ability.rb
# Allow user to CRUD pictures belonging to own gallery
can :manage, Picture, gallery: {user: user}
In the controller you can then do stuff like this:
# picture_controller.rb
# assuming a nested route, e.g. galleries/1/pictures
load_and_authorize_resource :gallery
load_and_authorize_resource :picture, through: :gallery
This will make sure that each user only sees his or her own pictures.
Restricting number of pictures in gallery
I think your approach with the validation is okay.
I would simplify it thus:
validate :quota
private
def quota
return unless user
if gallery.pictures.count > 4
errors[:base] << "Maximum photos uploaded, delete to upload more"
end
end
The error message should probably go into a locale file.
Creating Gallery automatically for each user
To do this, make sure that the Gallery model has a belong_to association to User. Then create the gallery in a callback in the User model:
# models/user.rb
after_create :setup_gallery
private
def setup_gallery
Gallery.create(user: self)
end
General notes
When you define your has_many relations, you should use plural names, like has_many :users or has_many :galleries.
Related
I am having challenges assigning a current user a role in a team the user is creating. I want to assign the user that creates the team the role of the captain which could be changed later.
I'm currently using the create_asociation method that comes with has_one relationship, as this instantiates the values of the associated model, which i want to be instantiated with the current user but get the error Can't mass assign protected attribute: captain. Captain is a self join model with user as i will like to use captain.teammates and team.captain.
Below are the models involved.
User and Captain Model
class User < ActiveRecord::Base
has_one :profile
has_many :teammates, :class_name => "User", :foreign_key => "captain_id"
belongs_to :captain, :class_name => "User"
belongs_to :team
# before_create :build_profile
after_create :build_default_profile
accepts_nested_attributes_for :profile
attr_accessible :email, :password, :password_confirmation, :profile_attributes, :captain_id
def build_default_profile
Profile.create(user_id: self.id)
end
has_secure_password
before_save { email.downcase! }
before_save :create_remember_token
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
Team Model
class Team < ActiveRecord::Base
has_many :profiles, through: :users
has_one :captain, :class_name => "User", foreign_key: :captain_id
has_one :result, as: :result_table
attr_accessible :teamname, :color, :result_attributes, :captain_attributes
after_create :build_result_table
after_create :build_default_captain
accepts_nested_attributes_for :profiles
accepts_nested_attributes_for :captain
accepts_nested_attributes_for :result
def build_result_table
Result.create(result_table_id: self.id, result_table_type: self.class.name)
end
def build_default_captain
# Team.captain = User
# Captain.create(team_id: self.id, captain_id: user.id)
end
end
User Controller
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:index, :edit, :update, :destroy]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: :destroy
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save!
sign_in #user
flash[:success] = "Welcome to the JHDC Mini Olympics Web Application; Thanks for singing Up"
redirect_to user_profile_path(#user, #profile)
else
flash[:error_messages]
render 'new'
end
end
def show
#user = User.find(params[:id])
end
def index
#users = User.paginate(page: params[:page])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile Updated"
redirect_to user_profile_path(#user, #profile)
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted."
redirect_to users_url
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
end
Team Controller
class TeamsController < ApplicationController
def new
#team = Team.new
end
def create
#team = Team.new(params[:team])
#captain = #team.create_captain(captain: current_user)
if current_user.admin?
if #team.save!
flash[:success] = "Team created."
redirect_to #team
else
flash[:error_messages]
render 'new'
end
else
flash[:error] = "Sorry, you don't have the authority to create a Team"
redirect_to current_user
end
end
def index
#teams = Team.paginate(page: params[:page])
end
def show
#team = Team.find(params[:id])
end
def edit
if current_user.admin?
#team = Team.find(params[:id])
else
flash[:error] = "Sorry you dont have the authourity to edit a Team"
redirect_to current_user
end
end
def update
#team = Team.find(params[:id])
if #team.update_attributes(params[:team])
flash[:success] = "Team Updated"
redirect_to #team
else
render 'edit'
end
end
def destroy
Team.find(params[:id]).destroy
flash[:success] = "Team is deleted."
redirect_to teams_url
end
private
def team_params
params.require(:team).permit(:teamname, :color)
end
end
The admin is currently a way i'm using to restrict the user that can create a team but i plan to use gems like declarative authorization to create role based authorization. Thanks
The error you are getting is because the attribute :captain is not declared as attr_accessible
Either set the attribute :captain in your list of attr_accessible for the User model, or change the code form
Captain.create(team_id: self.id, captain_id: user.id)
to
captain = Captain.new
captain.team_id = self.id
captain.captain_id = user.id
captain.create
in this way, the attribute won't be set by mass-assignment and won't raise the error
Edited
After checking your code twice, just realized that you don't have a Captain model, actually :captain is a relation for the user and a relation from the Team to the User.
So on Team model, take off the build_default_captain stuff and the after_create :build_default_captain, I would say to replace with something like
after_save :set_default_captain
def set_default_captain
if captain_id_changed?
profiles.each do |user|
user.captain = captain
user.save
end
end
end
so every time the captain_id change for the model, you change the captain_id of all its profiles (users)
Then on the Team controller, on the action create, instead of
#team = Team.new(params[:team])
#captain = #team.create_captain(captain: current_user)
do something like
#team = Team.new(params[:team])
#team.captain = current_user
if current_user.admin?
if #team.save!
current_user.update_attribute(:team_id, #team.id)
flash[:success] = "Team created."
redirect_to #team
else
flash[:error_messages]
render 'new'
end
else
flash[:error] = "Sorry, you don't have the authority to create a Team"
redirect_to current_user
end
so on the last part of the code, you set the captain of the team to the current user and set the user team to the current team once its saved, you can also improve the code with current_user.build_team to avoid saving current_user.update_attribute
I have tried to do this but was unsuccessful. There's also no postings covering this which is odd considering selecting Default photo is a option for every social network. I have the CarrierWave gem and I want to set it up so that the user can select their ProfileImage (default image) from photos they have already uploaded. This photo would be used site wide. It's like having an avatar, however the articles out there only show how to upload a avatar as oppose to selecting a avatar from your uploaded photos. I am sure this will be helpful to other people since this is a common feature.
Photos controller:
def new
#photo = Photo.new
end
def create
#photo = Photo.new(params[:photo])
#photo.user = current_user
if #photo.save
flash[:notice] = "Successfully created photos."
redirect_to :back
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(params[:id])
end
def update
#photo = Photo.find(params[:id])
if #photo.update_attributes(paramas[:photo])
flash[:notice] = "Successfully updated photo."
redirect_to #photo.gallery
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to #photo.gallery
end
end
User model:
# It is setup so no gallery is created, and photos are associated with the user.
private
def setup_gallery
Gallery.create(user: self)
end
Photo model:
attr_accessible :title, :body, :gallery_id, :name, :image, :remote_image_url
belongs_to :gallery
has_many :gallery_users, :through => :gallery, :source => :user
belongs_to :user
mount_uploader :image, ImageUploader
LIMIT = 5
validate do |record|
record.validate_photo_quota
end
def validate_photo_quota
return unless self.user
if self.user.photos(:reload).count >= LIMIT
errors.add(:base, :exceeded_quota)
end
end
end
You could set up the user model to be linked directly to a default photo.
class User < ActiveRecord::Base
belongs_to :default_photo, :class_name => "Photo"
end
You'd also need to add a default_photo_id column to the users table.
Then provide an interface that allows the user to browse all of their photos. In the UI you could have a button that says "Make default" (or whatever), and when the user clicks on that button it triggers a controller action that looks something like this:
def choose_default_photo
#photo = Photo.find params[:photo_id]
current_user.default_photo = #photo
redirect_to '/profile' # or wherever you wan to send them
end
Then whenever you need to reference the model for the default photo you'd just use:
current_user.defaut_photo
You should also take care of scenario when you destroy the default image . you should set default_photo_id to nil or to any other photo if you want.
I am using CarrierWave as my photo albums, and I am trying to setup so I can prevent users from only being able to upload maximum 5 photos to their gallery. However I am getting back a "undefined method `user'" error when clicking on the Upload Photo button with page title "NoMethodError in PhotosController#create"
Photo.rb:
class Photo < ActiveRecord::Base
attr_accessible :title, :body, :gallery_id, :name, :image, :remote_image_url
belongs_to :gallery
mount_uploader :image, ImageUploader
LIMIT = 5
validate do |record|
record.validate_photo_quota
end
def validate_photo_quota
return unless self.user
if self.user.photos(:reload).count >= LIMIT
errors.add(:base, :exceeded_quota)
end
end
end
Photos_controller:
class PhotosController < ApplicationController
def new
#photo = Photo.new(:gallery_id => params[:gallery_id])
end
def create
#photo = Photo.new(params[:photo])
if #photo.save
flash[:notice] = "Successfully created photos."
redirect_to #photo.gallery
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(params[:id])
end
def update
#photo = Photo.find(params[:id])
if #photo.update_attributes(paramas[:photo])
flash[:notice] = "Successfully updated photo."
redirect_to #photo.gallery
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to #photo.gallery
end
end
I thought I previously had user defined unless it has to be done for every controller?
You're calling self.user into the Photo model. The keyword self in that case represent an instance of photo. By your definition, a photo belongs to a gallery and therefore, there is no user to be called from photo.
If a gallery belongs to a user, then you should be able to call self.gallery.user to select the user owner of that photo.
You can also define a has_many :through association so as you can directly call the user from that photo, or retrive all the photos from that user.
This can be done following the documentation. In your case:
class User < ActiveRecord::Base
has_many :galeries
has_many :photos, :through => :galeries
end
class Photo < ActiveRecord::Base
belongs_to :user, :through => :gallery
belongs_to :gallery
end
class Gallery < ActiveRecord::Base
belongs_to :user
has_many :photos
end
Then you should be able to call photo.user and get the owner of the picture.
I have 3 models in my Rails 3 app:
User
model:
has_many :videos, :dependent => :destroy
controller:
before_filter :signed_in_user
def show
#user = User.find(params[:id])
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_path, notice: "Please sign in."
end
end
Video
model:
belongs_to :user
has_many :surveys, :dependent => :destroy
controller:
before_filter :signed_in_user
def signed_in_user
unless signed_in?
store_location
redirect_to signin_path, notice: "Please sign in."
end
end
def show
#video = Video.find(params[:id])
#original_video = #video.panda_video
#h264_encoding = #original_video.encodings["h264"]
#surveys = Survey.all
#user = User.find(params[:id])
end
Survey
model:
belongs_to :video
controller:
before_filter :signed_in_user
def signed_in_user
unless signed_in?
store_location
redirect_to signin_path, notice: "Please sign in."
end
end
def show
#survey = Survey.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #survey }
end
end
So in my app, a User has many Videos, and each Video has many Surveys (a.k.a. Reviews). Think of how Rotten Tomatoes works. A user has be signed in to either access a video, or write a review. Any review that user submits, while signed in, is automatically associated with that user...and this is what I'm trying to figure out with my app.
How do I associate the user id with the review? Right now, when a user is signed in, his name is automatically associated with all the reviews, whether he wrote them or not.
You want to use your Video class as a join model via the through option:
User
has_many :surveys, :through => :videos
Survey
has_one :user, :through => :video
This should let you do:
#user.surveys
#survey.user
Is there some reason this isn't as simple as:
User
has_many :surveys
Survey
belongs_to :user
If so, more info/code is required for a useful answer.
When a user edits something in my application, they're forced to re-upload their image via paperclip even if they aren't changing it. Failing to do so will cause an error, since I validate_presence_of :image. This is quite annoying.
How can I make it so Paperclip won't update its attributes if a user simply doesn't supply a new image on an edit?
The photo controller is fresh out of Rails' scaffold generator. The rest of the source code is provided below.
models/accommodation.rb
class Accommodation < ActiveRecord::Base
attr_accessible :photo
validates_presence_of :photo
has_one :photo
has_many :notifications
belongs_to :user
accepts_nested_attributes_for :photo, :allow_destroy => true
end
controllers/accommodation_controller.rb
class AccommodationsController < ApplicationController
def index
#accommodations = Accommodation.all
end
def show
#accommodation = Accommodation.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:error] = "Accommodation not found."
redirect_to :home
end
def new
#accommodation = current_user.accommodations.build
#accommodation.build_photo
end
def create
#accommodation = current_user.accommodations.build(params[:accommodation])
if #accommodation.save
flash[:notice] = "Successfully created your accommodation."
redirect_to #accommodation
else
#accommodation.build_photo
render :new
end
end
def edit
#accommodation = Accommodation.find(params[:id])
#accommodation.build_photo
rescue ActiveRecord::RecordNotFound
flash[:error] = "Accommodation not found."
redirect_to :home
end
def update
#accommodation = Accommodation.find(params[:id])
if #accommodation.update_attributes(params[:accommodation])
flash[:notice] = "Successfully updated accommodation."
redirect_to #accommodation
else
#accommodation.build_photo
render :edit
end
end
def destroy
#accommodation = Accommodation.find(params[:id])
#accommodation.destroy
flash[:notice] = "Successfully destroyed accommodation."
redirect_to :inkeep
end
end
models/photo.rb
class Photo < ActiveRecord::Base
attr_accessible :image, :primary
belongs_to :accommodation
has_attached_file :image,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
end
You shouldn't need #accommodation.build_photo anywhere else than in new action.