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.
Related
Making a site in with three main models: Users, Posts, and Gyms. Users should be able to post either from their own model (User.post), or, if they are the admin of a gym, from the Gym's model (Gym.post).
I'm using the same post controller and post form to post fro either the gym or the user, but the controller "Create" action can't distinguish between the two.
class PostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
if (gym.gym_admin == current_user.id)
#post = gym.posts.build(post_params)
if #post.save
flash[:success] = "Post!"
redirect_to "/gyms/#{gym.id}"
else
#feed_items = []
render 'static_pages/home'
end
else
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Post!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
end
def destroy
#post.destroy
flash[:notice] = "Post deleted"
redirect_to request.referrer || root_url
end
private
def post_params
params.require(:post).permit(:post_type, :title, :content, :picture, :body_parts,
:duration, :equipment, :calories, :protein,
:fat, :carbs, :ingredients, :tag_list,
:postable_id, :postable_type)
end
def correct_user
#post = current_user.posts.find_by(id: params[:postable_id])
redirect_to root_url if #post.nil?
end
def gym
#gym = Gym.find_by(params[:id])
end
end
And the Models:
class Post < ApplicationRecord
belongs_to :user
belongs_to :gym
belongs_to :postable, polymorphic: true
class User < ApplicationRecord
has_many :posts, as: :postable, dependent: :destroy
has_many :gyms
class Gym < ApplicationRecord
has_many :posts, as: :postable, dependent: :destroy
belongs_to :user
Rught now, this create action only creates posts from the gym's model; if I remove the first half of the conditional, it will only post from the User model.
Any help is greatly appreciated, thank you
I would be curious what gym.gym_admin (and consequently the whole line below 'def create') evaluates to since I don't see in referenced anywhere else.
My suspicion is that you would want to change
if (gym.gym_admin == current_user.id)
to
if (gym.gym_admin.id == current_user.id)
or
if (gym.gym_admin == current_user)
once that relationship is working correctly.
Also, could post be built independently of whether a user is a gym admin and send the post params the gym_id if applicable. Then accessed either through:
/gym/:id
#posts = Post.where('gym_id = ?', params[:id])
or
/user/:id
#posts = Post.where('user_id = ?', params[:id])
I fixed it my removing the conditional logic altogether; I just made two separate custom actions in the controller and called them from different links. ie. :actions => create_gym / create_user
I need to be able to "destroy" group ownership when I "destroy" membership of a group if a user is the owner of the group. I have "User", "Group/Cliq", and "Group/CliqMembership" models. When a user creates the group they are the "owner" of the group. Other users can then join the group. When a user leaves the group the membership association for that user and group is destroyed. However, when an owner leaves the group it only removes the "membership" and not the "ownership". I feel like there should be an easy solution, but I'm kind of stuck.
For clarity: Cliqs = Groups; the question is: how do I delete the ownership association and the membership association at the same time? When an "owner" leaves a group I want it to destroy their "group ownership" and their "group membership". As an aside: how would I make the owned group "destroy dependent" when the "owner" leaves?
Here are my models:
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships
has_many :members, through: :cliq_memberships, source: :user
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class User < ActiveRecord::Base
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq', dependent: :destroy
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
.
.
.
end
And my controllers:
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_cliq(cliq_params)
#cliq.members << current_user
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
#cliq = current_user.owned_cliq.find(params[:id])
flash[:alert] = "Are you sure you want to delete your Cliq? Your Cliq and all of its associations will be permanently deleted."
#cliq.destroy
if #cliq.destroy
redirect_to current_user
flash[:notice] = "You deleted the Cliq."
else
redirect_to current_user
#set up error handler
flash[:notice] = "Failed to delete Cliq."
end
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
class CliqMembershipsController < ApplicationController
def show
end
def create
#Cliq or Cliq_ID?
#cliq = Cliq.find(params[:cliq])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
#cliq.members << current_user
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
#Set up multiple error message handler for rejections/already a member
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq_membership.destroy
if #cliq_membership.destroy
flash[:notice] = "You left the Cliq."
redirect_to user_path(current_user)
else
end
end
end
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#uploads = Upload.all
#cliq_memberships = CliqMembership.all
#cliqs = Cliq.all
end
end
In your CliqMemberships controller's destroy action, right before destroying the CliqMembership, you can check for the current_user's ownership of the group just like you're checking for his/her membership. I'm assuming you want an implementation wherein if the owner leaves the group, you want the group to automatically be destroyed too, along with all its memberships. (Correct me if I'm wrong on that.) In that case, you can add dependent: :destroy to has_many :cliq_memberships
If the current_user happens to be the owner, you can destroy the cliq altogether which would in turn destroy its cliq_memberships too.
You can do it like this in CliqMemershipsController's destroy action.
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq = #cliq_membership.cliq
if #cliq.owner == current_user
#cliq.destroy
flash[:notice] = "The Cliq is destroyed"
redirect_to user_path(current_user)
else
# destroy only the membership
flash[:notice] = "You left the Cliq."
redirect_to user_path(current_user)
end
end
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'm getting this error for my products and user table.
--Couldn't find user without an id
def set_user
#user = User.find(params[:user_id])
end
I have nested the routes like so..
resources :users do
resources :products do
resources :reviews
end
end
and here is my products controller..
class ProductsController < ApplicationController
before_action :require_signin, except: [:index, :show]
before_action :set_user
def index
#products = #user.products
end
def show
#product = Product.find(params[:id])
end
def edit
#product = Product.find(params[:id])
end
def update
#product = Product.find(params[:id])
if #product.update(product_params)
redirect_to [#user, #product], notice: "Product successfully updated!"
else
render :edit
end
end
def new
#product = #user.products.new
end
def create
#product = #user.products.new(product_params)
#product.user = current_user
if #product.save
redirect_to user_products_path(#product, #user), notice: "Product successfully created!"
else
render :new
end
end
def destroy
#product = Product.find(params[:id])
#product.destroy
redirect_to user_products_path(#product, #user), alert: "Product successfully deleted!"
end
private
def product_params
params.require(:product).permit(:title, :description, :posted_on, :price, :location, :category)
end
def set_user
#user = User.find(params[:user_id])
end
end
All i am trying to do is associate the user and product so the product belongs_to user, and the user has_many products.
class Product < ActiveRecord::Base
belongs_to :user
has_many :reviews
class User < ActiveRecord::Base
has_secure_password
has_many :reviews, dependent: :destroy
has_many :products, dependent: :destroy
As other users have mentioned the params[:user_id] value is probably nil.
That said, you already appear to have a current_user defined in the scope of the controller. I see it referenced in the create action. I'd bet that it was set by the require_sign_in before_action. Given what I think you are trying to do, it probably makes your set_user before_action a bit redundant.
You can probably just refer to current_user in your controller anywhere you are currently using #user. Alternatively, you might set #user = current_user in the set_user before_action.
SideNote:
Looking a bit closer at your create action:
def create
#product = #user.products.new(product_params)
#product.user = current_user
if #product.save
redirect_to user_products_path(#product, #user), notice: "Product successfully created!"
else
render :new
end
end
Correct me if I'm wrong but I believe doing something like #model.association.new sets the model_id for the newly created association object so I would change the two lines
#product = #user.products.new(product_params)
#product.user = current_user
to simply be
#product = current_user.products.new(product_params)
For any action of your controller you should pass user_id param.
The reason of error is params[:user_id] equal nil
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.