I posted a similar question earlier and I thought that I had fixed this problem but I was wrong. I thought that the activity wasn't being deleted but turns out that it was deleting activity it was just the wrong one. I don't have this problem with any of my other models and this only happens on one of my apps. My forked app which has the exact same code works correctly. I don't know why this is happening.
projects_controller.rb
class ProjectsController < ApplicationController
before_filter :find_project, only: [:edit, :update, :destroy]
def create
params[:project][:about] = sanitize_redactor(params[:project][:about])
#project = current_member.projects.new(params[:project])
respond_to do |format|
if #project.save
current_member.create_activity(#project, 'created')
format.html { redirect_to #project }
format.json { render json: #project, status: :created, location: #project }
else
format.html { render action: "new" }
format.json { render json: #project.errors, alert: 'Please make sure all required fields are filled in and all fields are formatted correctly.' }
end
end
end
def destroy
#project = Project.find(params[:id])
#activity = Activity.find_by_targetable_id(params[:id])
if #activity
#activity.destroy
end
#project.destroy
respond_to do |format|
format.html { redirect_to profile_projects_path(current_member) }
format.json { head :no_content }
format.js
end
end
def find_project
#project = current_member.projects.find(params[:id])
end
end
member.rb
class Member < ActiveRecord::Base
def create_activity(item, action)
activity = activities.new
activity.targetable = item
activity.action = action
activity.save
activity
end
end
Migration
class CreateActivities < ActiveRecord::Migration
def change
create_table :activities do |t|
t.integer :member_id
t.string :action
t.integer :targetable_id
t.string :targetable_type
t.timestamps
end
add_index :activities, :member_id
add_index :activities, [:targetable_id, :targetable_type]
end
end
****EDIT****
activity.rb
class Activity < ActiveRecord::Base
belongs_to :member
belongs_to :targetable, polymorphic: true
acts_as_votable
self.per_page = 36
def self.for_member(member, options={})
options[:page] ||= 1
following_ids = member.following_members.map(&:id).push(member.id)
where("member_id in (?)", following_ids).
order("created_at desc").
page(options[:page])
end
end
project.rb
class Project < ActiveRecord::Base
belongs_to :member
attr_accessible :about, :blurb, :category, :markers, :video, :website, :name, :avatar, :banner, :marker_list, :city
acts_as_votable
acts_as_followable
acts_as_ordered_taggable
acts_as_ordered_taggable_on :markers
acts_as_messageable
has_many :comments, as: :commentable, :dependent => :destroy
has_many :uploads, as: :uploadable, :dependent => :destroy
has_many :updates, as: :updateable, :dependent => :destroy
def to_param
"#{id}-#{name.parameterize}"
end
end
member.rb
class Member < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :email_confirmation, :password, :password_confirmation, :remember_me,
:full_name, :user_name, :pursuits, :avatar, :bio, :city, :state, :country, :pursuit_list,
:facebook, :twitter, :linkedin, :soundcloud, :youtube, :vimeo, :instagram, :flickr, :google, :pinterest, :blog, :website, :banner
has_many :medium, :dependent => :destroy
has_many :projects, :dependent => :destroy
has_many :events, :dependent => :destroy
has_many :statuses, :dependent => :destroy
has_many :activities, :dependent => :destroy
has_many :listings, :dependent => :destroy
has_many :comments, :dependent => :destroy
has_many :uploads, :dependent => :destroy
has_many :updates, :dependent => :destroy
has_many :assets, :dependent => :destroy
acts_as_follower
acts_as_followable
acts_as_ordered_taggable
acts_as_ordered_taggable_on :pursuits
acts_as_voter
acts_as_messageable
def to_param
user_name
end
def name
user_name
end
def mailboxer_email(object)
return user_name
end
def create_activity(item, action)
activity = activities.new
activity.targetable = item
activity.action = action
activity.save
activity
end
end
Interesting...
--
Activity Model
You mention it's the Activity model which doesn't destroy correctly. With this in mind, you'll want to look at all the steps contributing to the destroy mechanism:
def destroy
...
#activity = Activity.find_by_targetable_id(params[:id])
#activity.destroy if #activity
First things first - what is targetable_id? Also, if you're using Rails 4, you'll be able to use the find_by method with a hash of attributes:
#activity = Activity.find_by targetable_id: params[:id]
This could be the main cause of the issue - the above code will basically look for any Activity records with the targetable_id attribute having the same id parameter as you passed from your request
I'm not a betting man, but I'd surmise this is where the issue lies
Polymorphic
Okay, I found the problem.
You're trying to call a polymorphic association without referencing it correctly. Polymorphic associations allow you to create an ActiveRecord association for multiple models, storing the associative data in a single table / model:
Notice how the above example (as your code) includes ______type column? This is VITAL to your solution - as it what stores the model you saved the ID for:
When you save data in ActiveRecord associations, it uses something called a foreign_key to define the associative data in your model. This foreign key is normally something like activity_id or similar, as you know already.
The difference is that your association is a polymorphic association, meaning you can store multiple model types in a single table. This means that ActiveRecord / Rails will have to refer to the model you're saving, which is where the _type column comes in
My theory is that your local database will have several models saved in your "polymorphic" datatable - meaning when you look for an ID, it's bringing back a record which isn't the correct model
Fix
I would recommend the following:
#app/models/project.rb
Class Project < ActiveRecord::Base
has_many :activities, as: :targetable, dependent: :destroy
end
#app/models/activity.rb
Class Activity < ActiveRecord::Base
belongs_to :targetable, polymorphic: true
end
I don't know your model associations, so I just made the above up. If you set up your associations as above, you'll be able to call the following:
#app/controllers/projects_controller.rb
Class ProjectController < ApplicationController
def destroy
#project = Project.find params[:id] # should find project.activities too
#project.destroy
end
end
I used the above for several important reasons:
You're using the same ID for both objects (means they're associated)
You can use the dependent: :destroy method
The bottom line is you will be able to destroy just the Project object, and have its dependants destroyed too, achieving your desired outcome
Related
first of all sorry for English
So i already have "user - posts" one to many association, which means that each post can have just ONE author, and now i want to add "favorite posts" button to user profile, and "add to favorite" button to each post, so the question is how to implement this correct way? should i rework my user - post association?
or create some another model? I,m a bit confused. Thank in advance !
Actually i want this result :
#user.posts #return all posts created by this user
#user.favorite_posts #return posts added to favorites by this user
Here is my User model:
class User < ApplicationRecord
mount_uploader :avatar, ImageUploader
validates :username, presence: true, uniqueness: true, length: {in: 3..20}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :ratings
enum role: [ :user, :admin ]
def calculate_average
ratings.blank? ? 0 : ratings.map(&:value).inject(:+) / ratings.count.to_f
end
end
Post model:
class Post < ApplicationRecord
mount_uploader :image, ImageUploader
validates :body, presence: true
validates :title, presence: true, length: { maximum: 50}
belongs_to :user
has_many :comments, dependent: :destroy
end
EDIT
Alright look how i've done this, it works exactly the way I wanted it.
Here is my user model:
class User < ApplicationRecord
mount_uploader :avatar, ImageUploader
validates :username, presence: true, uniqueness: true, length: {in: 3..20}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :ratings
has_many :favorites, dependent: :destroy
has_many :favorite_posts, through: :favorites, source: "post"
enum role: [ :user, :admin ]
def calculate_average
ratings.blank? ? 0 : ratings.map(&:value).inject(:+) / ratings.count.to_f
end
end
You need many-to-many relationships for the favorite post, at first run this command to create a table favorite_posts
rails g model FavoritePost user:references post:references
Then
rails db:migrate
Then add these to your model would look like this:
#=> model/user.rb
class User < ApplicationRecord
has_many :favorite_posts, dependent: :destroy # or you can use only this line except second if you will face any problem
has_many :posts, through: :favorite_posts
end
#=> model/post.rb
class Post < ApplicationRecord
has_many :favorite_posts, dependent: :destroy
has_many :users, through: :favorite_posts
end
#=> model/favorite_post.rb
class FavoritePost < ApplicationRecord
belongs_to :user
belongs_to :post
end
That was relation part, now create a favorite post part. For the fresh code you can create a controller, i.e.
rails g controller favorites
Then your routes file:
resources :favorites
An example of the new routes using rake routes:
favorites GET /favorites(.:format) favorites#index
POST /favorites(.:format) favorites#create
new_favorite GET /favorites/new(.:format) favorites#new
edit_favorite GET /favorites/:id/edit(.:format) favorites#edit
favorite GET /favorites/:id(.:format) favorites#show
PATCH /favorites/:id(.:format) favorites#update
PUT /favorites/:id(.:format) favorites#update
DELETE /favorites/:id(.:format) favorites#destroy
In your view file add something like this:
# For creating favorite
<%= link_to "Favorite", favorites_path(user: current_user, post: post.id), class: 'btn bf-save-btn', method: :post, data: {disable_with: "Saving..."}, title: "Add to favorite" %>
# For deleting favorite list
<%= link_to "Unfavorite", favorite_path(post.id), class: 'btn af-save-btn', method: :delete, data: {disable_with: "Removing...."}, title: "Remove from favorite" %>
In favorites_controller.rb:
def index
#saves = current_user.favorite_post
end
# index.html.erb
<% #saves.each do |fav| %>
<%= link_to fav.post.post_title, post_path(fav.post) %>
<% end %>
def create
#save = FavoritePost.new(post_id: params[:post], user: current_user)
respond_to do |format|
if #save.save
flash[:success] = 'Saved'
format.html { redirect_to request.referer }
format.xml { render :xml => #save, :status => :created, :location => #save }
else
format.html { redirect_to request.referer }
format.xml { render :xml => #save.errors, :status => :unprocessable_entity }
end
end
end
def destroy
post = Post.find(params[:id])
#save = FavoritePost.where(user_id: current_user.id, post_id: post.id).first
respond_to do |format|
if #save.destroy
flash[:error] = 'Unsaved'
format.html { redirect_to request.referer, status: 303 }
format.js { redirect_to request.referer, status: 303 }
# format.xml { head :ok }
end
end
end
That's it for favorite / unfavorite functionality. Now you need to create some logic for when to show Favorite and when Unfavorite.
For this requirements has many ways, at first you need to understand this then you can whatever you want.
Also, to achieve this without reloading your page you can try some Ajax.
Update
class User < ApplicationRecord
mount_uploader :avatar, ImageUploader
validates :username, presence: true, uniqueness: true, length: {in: 3..20}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :ratings
# Newly added
has_many :favorite_posts, dependent: :destroy # or you can use only this line except second if you will face any problem
has_many :posts, through: :favorite_posts
enum role: [ :user, :admin ]
def calculate_average
ratings.blank? ? 0 : ratings.map(&:value).inject(:+) / ratings.count.to_f
end
end
Hope it will help.
Create a new model for 'UserFavoritePost' stored post_id and user_id. And create a custom association for favorite_posts
class UserFavoritePost < ApplicationRecord
belongs_to :post
belongs_to :user
end
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :user_favorite_posts
has_many :favorite_posts, throught: :user_favorite_posts, class: 'Post'
end
fool-dev's answer does not provide a direct access to favorite posts, so the index view requires a loop. Prasanna's approach solves this, but his answer has been unfairly accused of incomplete and plagiarism :-). So here is the complete approach:
You need a many to many relationship, that´s true, so you need a join model and table. But this model is auxiliary. No important logic should be there, and I don´t think it deserves a controller or views.
class User < ApplicationRecord
has_many :posts, dependent: :destroy # Posts created by this user
has_many :favs, dependent: :destroy
has_many :fav_posts, through: :favs # Favorite posts for this user
end
class Post < ApplicationRecord
belongs_to :user
has_many :favs, dependent: :destroy
has_many :fav_users, through: :favs # Users who have this post as favorite
end
class Fav < ApplicationRecord
belongs_to :user
belongs_to :post
end
This allows to access all posts created by the user and all his favorite posts using two different methods in the user class.
#posts = current_user.posts # Posts created by this user
#fav_posts = current_user.fav_posts # Favorite posts
In the view:
<h1><% current_user.name %></h1>
<h2>Your posts</h2>
<%= render #posts %>
<h2>Your favorite posts from other users</h2>
<%= render #fav_posts %>
You don't need a controller to create, view or delete favorite posts. Just handle this logic in the User or Post controllers. For example, to favorite or unfavorite a post just add fav and unfav methods in the PostsController.
def fav
current_user.fav_posts << Post.find(params[:id])
end
def unfav
current_user.favs_posts.destroy(Post.find(params[:id]))
end
In the view:
<%= link_to "Favorite", fav_post_path(id: post.id) %>
<%= link_to "Unfavorite", unfav_post_path(id: post.id) %>
You should add these methods in your routes:
post '/posts/:id/fav', to: 'posts#fav', as: 'fav_post'
post '/posts/:id/unfav', to: 'posts#unfav', as: 'unfav_post'
I have created a web app that has user profiles, where users can search for fellow users based on interests, as well as post + attend events etc.. How might I add a feature where users can see who 'Recently visited' a certain event?
event.rb
class Event < ActiveRecord::Base
attr_accessible :title, :description, :location, :date, :time, :event_date
acts_as_commentable
has_many :comments, as: :commentable
belongs_to :user
has_many :event_participants, dependent: :destroy
has_many :participants, through: :event_participants, source: :user
end
user.rb
class User < ActiveRecord::Base
include PgSearch
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable,
:omniauth_providers => [:facebook, :twitter, :linkedin]
attr_accessible :email, :password, :password_confirmation, :zip, :gender, :remember_me, :first_name, :last_name,
:birthday, :current_password, :occupation, :address, :interests, :aboutme, :profile_image,
:photos_attributes, :age, :education, :ethnicity
has_many :authorizations, :dependent => :destroy
has_many :comments
has_many :events
has_many :photos, as: :attachable
has_many :questions
has_many :sent_messages, class_name: 'Message', foreign_key: :sender_id
has_many :received_messages, class_name: 'Message', foreign_key: :receiver_id
accepts_nested_attributes_for :photos
mount_uploader :profile_image, ProfileImageUploader
end
event_participants.rb
class EventParticipant < ActiveRecord::Base
attr_accessible :user_id, :event_id
belongs_to :user
belongs_to :event
validates :user_id, uniqueness: {scope: :event_id}
end
events_controller snippit
class EventsController < ApplicationController
before_filter :authenticate_user!
def index
#users = User.all
#user = current_user
#events = Event.all
#interesting_people = #users.order("random()").first(5)
end
def new
#event = Event.new
end
def create
#event = current_user.events.new(event_params)
respond_to do |format|
if #event.save
format.html { redirect_to :back, notice: 'Event was successfully created.' }
format.json { render action: 'show', status: :created, location: #event }
format.js
else
format.html { render action: 'new' }
format.json { render json: #event.errors, status: :unprocessable_entity }
format.js
end
end
end
def show
#users = User.all
#user = current_user
#event = Event.find(params[:id])
#commentable = #event
#comment = #event.comments.new
#interesting_people = #users.order("random()").first(5)
end
Based on your code, it looks like you could do something like:
#event.event_participants.include(:users).order('created_at desc').limit(X).map(&:user)
You could also try without the include:
#event.event_participants.order('created_at desc').limit(X).map(&:user)
But that will do N+1 queries. The join will perform better.
Where in the above code, X would be how many participants you want to show.
I followed Michael Hartl's Rails Tutorial to implement a follower system similar to Twitter's (railstutorial.org). For now, I want to use this follower system to only display activities of users followed by the user currently logged in. I'm able to display all activities in the system on the /activities page with no issue. The Activities controller code that works is as follows:
class ActivitiesController < ApplicationController
def index
#activities = PublicActivity::Activity.all
end
end
The index page for the Activities controller lists all of the posts made by all users. However, I want to edit the controller code to only display the activities of followed users. How can I do this? All my attempts lead to no method errors. Here are relevant items:
User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :title, :location, :aboutme, :works_attributes
# attr_accessible :title, :body
validates_presence_of :first_name, :last_name
validates_uniqueness_of :first_name, :last_name, :email, :case_sensitive => false
has_many :works, dependent: :destroy
accepts_nested_attributes_for :works
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
has_many :posts, dependent: :destroy
def full_name
[first_name, last_name].join(" ")
end
def following?(other_user)
relationships.find_by_followed_id(other_user.id)
end
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
end
def unfollow!(other_user)
relationships.find_by_followed_id(other_user.id).destroy
end
end
Relationship model:
class Relationship < ActiveRecord::Base
attr_accessible :followed_id, :follower_id
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
User controller
class UsersController < ApplicationController
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
def show
#user = User.find(params[:id])
#posts = #user.posts.all
#works = #user.works.all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.followed_users.all
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.all
render 'show_follow'
end
def posts
#user = User.find(params[:id])
#posts = #user.posts.all
render 'show_post'
end
end
Relationships controller
class RelationshipsController < ApplicationController
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
I've been stuck on this problem for way too long. Any help would be greatly appreciated. Please feel free to ask questions if you need more information.
How can I edit the Activities controller so that it displays activity for followed users?
**EDIT: It works!!
Disclaimer: I don't know anything about the public_activity gem, but here's what the documentation suggests you need to do:
Set the "owner" attribute on your activities.
Filter the activities you display: PublicActivity::Activity.where(owner_type: "User", owner_id: current_user.followed_users.map {|u| u.id}).all (This is based on this sample code.)
I am working with a has_many :through association: Users join Groups through a Membership. I am having trouble with some methods I created in the user model to determine if the user is a member of a group, and to allow a user to join and leave a group. Using the console, memberships.find_by_group_id always returns nil. I am not sure why and I think it might be the way I have set up my has_many :through associations, though after looking it over many, many times and consulting railscasts/blogs it seems ok. If you want me to post more info, like the schema.db or something, let me know
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :groups, :through => :memberships
.
.
.
def member?(group)
memberships.find_by_group_id(group)
end
def join!(group)
memberships.create!(:group_id => group.id)
end
def leave!(group)
memberships.find_by_group_id(group).destroy
end
.
.
.
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
has_attached_file :photo, :styles => { :thumb => "100x100",
:small => "200x200" }
attr_accessible :name, :description, :private, :created_at, :group_id
attr_accessible :photo, :photo_file_name, :photo_content_type,
:photo_file_size, :photo_updated_at
end
class Membership < ActiveRecord::Base
attr_accessible :group_id
belongs_to :user
belongs_to :group
end
Here is the membership controller:
class MembershipsController < ApplicationController
def create
#group = Group.find(params[:membership][:group_id])
current_user.join!(#group)
respond_to do |format|
format.html { redirect_to #group }
format.js
end
end
def destroy
#group = Membership.find(params[:id]).group
current_user.leave!(#group)
respond_to do |format|
format.html { redirect_to #group }
format.js
end
end
def index
#memberships = Membership.all
end
end
I think what you are looking for is :has_and_belongs_to_many (N-N) association rather than :has_many (1-N) association.
It seems that each group can have many users as well.
Take a look at official rails guides at http://guides.rubyonrails.org/association_basics.html#has_and_belongs_to_many-association-reference
Using :has_and_belongs_to_many relationships, it will be really easy to add a collection(user) to the other collection(membership) .
For example
#membership.users << current_user
will add current_user to the membership
and
#membership.users.delete(current_user)
will delete current_user from the membership.
class Contact < ActiveRecord::Base
has_many :contact_company_profiles, :dependent => :destroy
accepts_nested_attributes_for :contact_company_profiles, :allow_destroy => true
has_many :companies, :through => :contact_company_profiles
has_many :phones, :as => :phoneable, :dependent => :destroy
accepts_nested_attributes_for :phones
end
class ContactCompanyProfile < ActiveRecord::Base
belongs_to :contact
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :contact_company_profiles
has_many :contacts, :through => :contact_company_profiles
has_many :phones, :as => :phoneable, :dependent => :destroy
end
For above specified models i want respond with JSON format through contact controller the code was working fine until i was accessing till companies the below specified command.
#contacts = Contact.find(:id)
respond_to do |format|
format.html
format.js
format.json { render :json=>#contacts.to_json(:include=>[:companies, :phones) }
format.xml { render :xml => #contacts }
end
But now i want json of nested Phone element of company in my contact controller. So kinldy help me in this regard.
Thanks
When I work on this kind of problem, I often end up overriding serializable_hash
It's the method that's used when generating json and xml. You just build up the hash to contain whatever you want. I often add what I want then pass it to the original. Plus, this way you never have to think about it in the controller. You can always just return it, and the object will do the right thing.
def serializable_hash(options = {})
# TODO exclude the id
options = {:include => [:address],
:except => [:created_at, :updated_at, :creating_user_id]}.merge(options ||= {})
super options
end