I'm trying to use the Mailboxer Gem and I ran into problems when creating a conversations controller.
Now when I call current_user.mailbox.inbox I get the following:
You tried to define a scope named "new" on the model "Mailboxer::Conversation", but Active Record already defined a class method with the same name.
I have removed my conversations controller and actions but this is still showing.
Is it possible that a Gem Scope and method are clashing? perhaps the rails g controller added files else where?
Below is the model where the scope is defined. (This is from the Mailboxer Gem)
class Mailboxer::Conversation < ActiveRecord::Base
self.table_name = :mailboxer_conversations
attr_accessible :subject if Mailboxer.protected_attributes?
has_many :opt_outs, :dependent => :destroy, :class_name => "Mailboxer::Conversation::OptOut"
has_many :messages, :dependent => :destroy, :class_name => "Mailboxer::Message"
has_many :receipts, :through => :messages, :class_name => "Mailboxer::Receipt"
validates :subject, :presence => true,
:length => { :maximum => Mailboxer.subject_max_length }
before_validation :clean
scope :participant, lambda {|participant|
where('mailboxer_notifications.type'=> Mailboxer::Message.name).
order("mailboxer_conversations.updated_at DESC").
joins(:receipts).merge(Mailboxer::Receipt.recipient(participant)).uniq
}
scope :inbox, lambda {|participant|
participant(participant).merge(Mailboxer::Receipt.inbox.not_trash.not_deleted)
}
scope :sentbox, lambda {|participant|
participant(participant).merge(Mailboxer::Receipt.sentbox.not_trash.not_deleted)
}
scope :new, lambda {|participant|
participant(participant).merge(Mailboxer::Receipt.trash)
}
scope :unread, lambda {|participant|
participant(participant).merge(Mailboxer::Receipt.is_unread)
}
scope :not_trash, lambda {|participant|
participant(participant).merge(Mailboxer::Receipt.not_trash)
}
#Mark the conversation as read for one of the participants
def mark_as_read(participant)
return unless participant
receipts_for(participant).mark_as_read
end
#Mark the conversation as unread for one of the participants
def mark_as_unread(participant)
return unless participant
receipts_for(participant).mark_as_unread
end
#Move the conversation to the trash for one of the participants
def move_to_trash(participant)
return unless participant
receipts_for(participant).move_to_trash
end
#Takes the conversation out of the trash for one of the participants
def untrash(participant)
return unless participant
receipts_for(participant).untrash
end
#Mark the conversation as deleted for one of the participants
def mark_as_deleted(participant)
return unless participant
deleted_receipts = receipts_for(participant).mark_as_deleted
if is_orphaned?
destroy
else
deleted_receipts
end
end
#Returns an array of participants
def recipients
return [] unless original_message
Array original_message.recipients
end
#Returns an array of participants
def participants
recipients
end
#Originator of the conversation.
def originator
#originator ||= original_message.sender
end
#First message of the conversation.
def original_message
#original_message ||= messages.order('created_at').first
end
#Sender of the last message.
def last_sender
#last_sender ||= last_message.sender
end
#Last message in the conversation.
def last_message
#last_message ||= messages.order('created_at DESC').first
end
#Returns the receipts of the conversation for one participants
def receipts_for(participant)
Mailboxer::Receipt.conversation(self).recipient(participant)
end
#Returns the number of messages of the conversation
def count_messages
Mailboxer::Message.conversation(self).count
end
#Returns true if the messageable is a participant of the conversation
def is_participant?(participant)
return false unless participant
receipts_for(participant).any?
end
#Adds a new participant to the conversation
def add_participant(participant)
messages.each do |message|
Mailboxer::ReceiptBuilder.new({
:notification => message,
:receiver => participant,
:updated_at => message.updated_at,
:created_at => message.created_at
}).build.save
end
end
#Returns true if the participant has at least one trashed message of the conversation
def is_trashed?(participant)
return false unless participant
receipts_for(participant).new.count != 0
end
#Returns true if the participant has deleted the conversation
def is_deleted?(participant)
return false unless participant
return receipts_for(participant).deleted.count == receipts_for(participant).count
end
#Returns true if both participants have deleted the conversation
def is_orphaned?
participants.reduce(true) do |is_orphaned, participant|
is_orphaned && is_deleted?(participant)
end
end
#Returns true if the participant has trashed all the messages of the conversation
def is_completely_trashed?(participant)
return false unless participant
receipts_for(participant).new.count == receipts_for(participant).count
end
def is_read?(participant)
!is_unread?(participant)
end
#Returns true if the participant has at least one unread message of the conversation
def is_unread?(participant)
return false unless participant
receipts_for(participant).not_trash.is_unread.count != 0
end
# Creates a opt out object
# because by default all particpants are opt in
def opt_out(participant)
return unless has_subscriber?(participant)
opt_outs.create(:unsubscriber => participant)
end
# Destroys opt out object if any
# a participant outside of the discussion is, yet, not meant to optin
def opt_in(participant)
opt_outs.unsubscriber(participant).destroy_all
end
# tells if participant is opt in
def has_subscriber?(participant)
!opt_outs.unsubscriber(participant).any?
end
protected
#Use the default sanitize to clean the conversation subject
def clean
self.subject = sanitize subject
end
def sanitize(text)
::Mailboxer::Cleaner.instance.sanitize(text)
end
end
As you can see you have:
scope :new, lambda {|participant|
participant(participant).merge(Mailboxer::Receipt.trash)
}
Which is already defined in ActiveRecord::Base
Mailboxer::Conversation.new
exists as a class method and a scope. Changing the scope name should be enough to fix it.
As pointed in the comments, reinstall the gem will be the fastest solution.
Related
I am creating an instance of a friendship between two users. When I create it in the rails console everything works as planned, however, when I actually create the friendship through the site the status column is left blank. If anyone can tell me why it would be really appreciated.
here is the model I have:
class Friendship < ApplicationRecord
attr_accessor :status, :accepted_at
belongs_to :user
belongs_to :friend, :class_name => "User"
def self.request(user,friend)
unless user == friend or Friendship.exists?(user: user, friend: friend)
transaction do
create(:user => user, :friend => friend, :status => 'pending')
create(:user => friend, :friend => user, :status => 'requested')
end
end
end
def self.accept(user,friend)
transaction do
accepted_at = Time.now
accept_one_side(user,friend,accepted_at)
accept_one_side(friend,user,accepted_at)
end
end
def self.accept_one_side(user,friend,accepted_at)
request = Friendship.where("user_id = #{user.id} AND friend_id = #{friend.id}")
request.update_all(:status => 'accepted', :accepted_at => accepted_at)
end
end
and this is my controller
class FriendshipsController < ApplicationController
def create
friend = User.where(email: params[:friend_email]).first
Friendship.request(current_user,friend)
redirect_to user_profile_path(current_user)
end
def friendship_params
params.require(:friendship).permit(:friend_email)
end
end
Friendship model has attr_accessor :status, which overrides the default getter setter methods provided by ActiveRecord for your status column.
I'm attempting to set limits on the amount of commenting users can do on particular post during the day. I have implemented the following (successfully) in my Post model to limit the amount of Posts they can create.
class Post < ActiveRecord::Base
validate :daily_limit, :on => :create
def daily_limit
# Small limit for users who just sign up
if author.created_at >= 14.days.ago
if author.created_posts.today.count >= 4
errors.add(:base, "Exceeds Your Daily Trial Period Limit(4)")
end
else
if author.created_posts.today.count >= author.post_limit_day
errors.add(:base, "Exceeds Your Daily Limit")
end
end
end
end
But, when I attempt to add similar restrictions to my Comment model
class PostComment < ActiveRecord::Base
validate :daily_limit, :on => :create
belongs_to :post, :counter_cache => true
belongs_to :user
def daily_limit
# Small limit for users who just sign up
if user.posted_comments.today.count >= 2
errors.add(:base, "Exceeds Your Daily Trial Period Limit(4)")
end
end
end
I am greeted with a undefined method 'posted_comments' for nil:NilClass error. I don't believe my user_id is being passed into my model correctly in order to access it with something like user.posted_comments.today.count>=2
My create action in my post_comments controller is as follows:
class PostCommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#post_comment = #post.post_comments.create(post_comment_params)
#post_comment.user = current_user
if #post_comment.save
redirect_to #post
else
flash[:alert] = "Comment Not Added"
redirect_to #post
end
end
end
and the my hacked down User model is as follows:
class User < ActiveRecord::Base
has_many :created_posts, class_name: 'Post', :foreign_key => "author_id",
dependent: :destroy
has_many :posted_comments, class_name: 'PostComment', :foreign_key =>"user_id", dependent: :destroy
end
Thanks.
You are assigning the user after "create" in your controller
#post_comment = #post.post_comments.create(post_comment_params)
#post_comment.user = current_user
Try this:
#post_comment = #post.post_comments.build(post_comment_params)
#post_comment.user = current_user
I am totally new to Ruby, and Rails. Currently, I am using helper methods. How can I write the same code as this in my Model 'User' so as to access all these variables from controller and view?
Writting code this way in helper is 100% functional:
module HomeHelper
def init(user_id)
#friends = Array.new
#followers = Array.new
#user = User.find_by_id(user_id) #Get User
#friends = #user.users #Get all his friends
#
#statuses = Array.new #
#friends.each do |friend| #
#statuses += friend.statuses #Get all statuses for 'a' friend, then loop
end #
#statuses += #user.statuses #
#statuses = #statuses.sort_by {|status| status.created_at}.reverse!
#friendsof = Array.new
#filtered_friendsof = Array.new
#friends.each do |friend|
#friendsof += friend.users
end
#friendsof.each do |friendof|
unless (#friends.include?(friendof))
if #user != friendof
#filtered_friendsof << friendof
end
end
end
end
#filtered_friendsof = #filtered_friendsof.uniq
end
Controller
class HomeController < ApplicationController
def index
#user_id=3
end
end
Model:
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many(:users,
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id")
#has_many :user_connections
end
Home controller:
class HomeController < ApplicationController
def index
#user = User.find(3)
end
end
User model:
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many :friends,
:class_name => 'User'
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id"
def combined_statuses
(friends.map(&:statuses) + statuses).flatten.
sort_by {|status| status.created_at}.reverse!
end
end
Now, you don't need your helper method and in your view you can use:
#user.friends # instead of #friends
#user.combined_statuses # instead of #statuses
I'll let you figure out the rest, but I hope you get the general idea of pushing the logic into the model.
Most of that logic belongs in the User model. Nothing else needs to be actually doing those computations, and the User model has access to all the relevant pieces. There are additionally several other improvements that can be made. I'll try to add comments below to indicate these improvements.
Model
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many :friends, # now you can just say user.friends
:class_name => 'User', # makes more sense semantically
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id"
def friends_statuses
(friends.map(&:statuses).flatten + statuses).sort_by!(&:created_at).reverse
# Ruby has many great methods for Arrays you should use.
# You can often avoid instantiating variables like the empty Arrays you have.
end
def second_order_friends
(friends.map(&:friends).flatten.uniq - friends) - [self]
end
end
Controller
class HomeController < ApplicationController
def index
user = User.find(7) # how do you decide which user you're displaying things for?
# this might be better off in 'show' rather than 'index'
# here you can call all the methods you have for 'User', such as:
# user.friends, user.statuses, user.friends_statuses, user.second_order_friends
# to make things accessible in the view, you just need an #variable, e.g.:
#friends = user.friends
#latest_statuses = user.friends_statuses.first(10)
end
We are looking to have Sender and Receiver attributes for each micropost that is entered on our site. The sender of the post, and the receiver whom it is directed to.
In other words, on each micropost that each user sees, we want the content, and just above or below the content of the post we want the sender shown and receiver shown. We also want users to be able to click on either the sender or the receiver and be linked directly to that profile.
How can we go about doing this? We are relatively new to rails and think additions need to be made in the Micropost model for this change to work. Or should the changes be made in the MicropostsController?
Micropost Model:
class Micropost < ActiveRecord::Base
attr_accessible :content, :belongs_to_id
belongs_to :user
validates :content, :presence => true, :length => { :maximum => 240 }
validates :user_id, :presence => true
default_scope :order => 'microposts.created_at DESC'
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{following_ids}) OR user_id = :user_id",
{ :user_id => user })
end
end
MicropostsController:
class MicropostsController < ApplicationController
before_filter :authenticate, :only => [:create, :destroy]
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.save
flash[:success] = "Posted!"
redirect_to current_user
else
#feed_items = []
render 'pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_path
end
end
To eliminate some confusion and make it a bit more railsy, I'd go with:
class Micropost < ActiveRecord::Base
belongs_to :sending_user, :class_name=>"User", :foreign_key=>"user_id"
belongs_to :receiving_user, :class_name=>"User", :foreign_key=>"belongs_to_id"
end
this will allow something like this in your view for a given Micropost object "#micropost":
<%= link_to(#micropost.sending_user.username, user_path(#micropost.sending_user)) %>
<%= link_to(#micropost.receiving_user.username, user_path(#micropost.receiving_user)) %>
*this assumes several things about the user object and routing, but should get you on the right path.
Two models, an Account model (has_many :users) and a User model (belongs_to :account, :dependent => :destroy).
My User model has the following:
def protect_master_user
unless User.find_all_by_account_id_and_is_master(self.account_id, true).count > 1
false
end
end
I'm trying to protect the master users from being deleted. How can I override this if the parent (Account) is deleted? I've tried :dependent => :destroy and :delete to no avail.
EDIT: fixed code.
There are two ways to do this: has_many :users, :dependent => :delete_all, or with an additional custom callback in Accounts. The :delete_all method is simpler, but not advised, because it means that none of your other call backs will happen on the user record.
The correct solution is a custom before_destroy callback in Account, working in tandem with the callback in user.
class Account < ActiveRecord::Base
has_many :users
before_destroy :destroy_users
protected
def destroy_users
users.each do |user|
u.account_id = nil
u.destroy
end
end
end
class User < ActiveRecord::Base
belongs_to :account
before_destroy :protect_master_user
protected
def protect_master_user
unless account_id.nil? ||! master ||
User.find_all_by_account_id_and_master(self.account_id, true).count > 1
errors.add_to_base "Cannot remove master user."
return false
end
end
end
If the account.id is nil we short circuit the unless and the destroy continues. Same goes for if the user is not a master user. Why do we need to check if there's more than one master user if the object being destroyed isn't a master user either?
Again, delete could be used in place of destroy. But it skips any *_destroy callbacks you have or will ahve in the future.
I had this very same question and conundrum recently and found the best way to deal with this was to handle it in the controller as I really only care if a user is trying to delete the last Master user rather than if the system is doing it:
class UsersController < ApplicationController
def destroy
user = current_account.users.find(params[:id].to_i)
if allow_removal_of_user?(user) && user.destroy
redirect_to users_path, :notice => "User sucessfully deleted"
else
flash[:error] = user.errors.empty? ? "Error" : user.errors.full_messages.to_sentence
render :edit
end
end
private
def allow_removal_of_user?(user)
if user == current_user
errors.add(:user_removal, "Can't delete yourself")
false
elsif user.only_user? || (user.master_users_for_account.count == 1 && user.master_role?)
errors.add(:user_removal, "Can't delete last Master user")
false
else
true
end
end
end
Hope this helps somebody!