How to limit Mailboxer inbox to conversations between specific users? - ruby-on-rails

I am creating a messaging system in which a particular user may be a member of one or more organizations. Therefore, if they are signed in to an organization they should only be able to see conversations with users from the same organization but I can't seem to find a way to figure out how to specify that in the query. For example:
recipients = current_org.users
#conversations = current_user.mailbox.inbox.conversations.where(participants.include?(recipients))
..or something along those lines.

I didn't find a great way to do this, but this is what I did for future reference. I added a class_eval in an initializer file on Mailboxer's Receipts model, creating a slightly modified version of their .recipients scope. It checks to see if there is more than one in the collection and calls .id on it, which was an issue before. It also gets the base_class of the first of the collection since they will all be the same.
Mailboxer::Receipt.class_eval do
scope :recipients, lambda { |recipient|
if recipient.is_a?(ActiveRecord::Associations::CollectionProxy)
where(:receiver_id => recipient.collect {|x| x.id },:receiver_type => recipient.first.class.base_class.to_s)
else
where(:receiver_id => recipient.id,:receiver_type => recipient.class.base_class.to_s)
end
}
end

Related

Query the database to check if the email provided in the invitation model already exists in the user model

I have successfully implemented an invite feature in my app. Each user has_many scoreboards and each scoreboard has_many invitations. The invitation model has two columns, recipient_name and recipient_email and an invitation email is sent to the recipient_email. All these features are working perfectly fine. The controller code for the create action is given below.
** scoreboard
** has_many :sent_invitations, :class_name => "Invitation"
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#invitation = #scoreboard.sent_invitations.build(invitation_params)
if #invitation.save # and the recipient_email doesn't exist in the user model database
# send the sign_up email
UserMailer.invitation_email(#invitation, #scoreboard).deliver_now
flash[:success] = "Invitation sent"
redirect_to new_scoreboard_invitation_path
#elsif
# if the user exists in the database
# send the email saying they've been invited to view a scoreboard
else
render new_scoreboard_invitation_path
end
end
end
As an extension of the feature, I also want to query the database to check if the recipient_email provided in the invitation model exists in the user model (column :email). I have emails set as unique when the user signs up, therefore, searching for the email will reveal whether the user is registered or not.
The problem is that I am not sure how to check if the recipient_email present in the invitation_table also exists in the user_table. I have tried several things.
I have tried saving the recipient_email's most recent record in a local variable and then querying the database for that record. I didn't think that was the correct way to implement this.
I also tested the code given below in the invitation's new action with a simple if and else statement to see what would happen. However, every time I send an email to a registered or non-registered user, It always prints "no". I am not exactly sure exactly how to approach this correctly. I know the exists? method would be used somewhere but not sure how to really use it. I have tried to keep it to the point and included the relevant pieces of code. However, if I missed anything, I can definitely include that. Any help would be greatly appreciated, Thanks!!
<% if User.where("email = ?", #invitation.recipient_email).exists? %>
<%= "Yes" %>
<% else %>
<%= "no" %>
<% end %>
You're on the right track with your usage of the .exists? method, but you're actually calling it incorrectly. The condition (which you have as a .where statement) is actually meant to be passed to the .exists? function as a parameter.
So, instead of saying:
if User.where("email = ?", #invitation.recipient_email).exists?
You actually want to say:
if User.exists?( email: #invitation.recipient_email )
.exists? takes a hash of field names paired with values. For more detail on the exists? method, see the official documentation.
Addendum
Regarding the hash notation (being passed to the .exists? method), in the most recent versions of Rails the "standard" way of passing parameters to ActiveRecord methods is in hash form. There are, however, certainly times when it is appropriate to use the question mark interpolation method you chose to employ. I'm only offering this comment to alleviate any confusion between the two different method calls. Each of the following would perform the same query:
.where("field = ?", value)
.where(:field => value)
.where(field: value)

Proper way to prevent ActiveRecord::ReadOnlyRecord?

I'm currently using Rails 2.3.9. I understand that specifying the :joins option in a query without an explicit :select automatically makes any records that are returned read-only. I have a situation where I would like to update the records and while I've read about different ways to approach it, I was wondering which way is the preferred or "proper" way.
Specifically, my situation is that I have the following User model with an active named scope that performs a JOIN with the subscriptions table:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
When I call User.active.all, the user records that are returned are all read-only, so if, for instance, I call update_attributes! on a user, ActiveRecord::ReadOnlyRecord will be raised.
Through reading various sources, it seems a popular way to get around this is by adding :readonly => false to the query. However, I was wondering the following:
Is this safe? I understand the reason why Rails sets it to read-only in the first place is because, according to the Rails documentation, "they will have attributes that do not correspond to the table’s columns." However, the SQL query that is generated from this call uses SELECT `users`.* anyway, which appears to be safe, so what is Rails trying to guard against in the first place? It would appear that Rails should be guarding against the case when :select is actually explicitly specified, which is the reverse of the actual behavior, so am I not properly understanding the purpose of automatically setting the read-only flag on :joins?
Does this seem like a hack? It doesn't seem proper that the definition of a named scope should care about explicitly setting :readonly => false. I'm also afraid of side effects if the named scoped is chained with other named scopes. If I try to specify it outside of the scope (e.g., by doing User.active.scoped(:readonly => false) or User.scoped(:readonly => false).active), it doesn't appear to work.
One other way I've read to get around this is to change the :joins to an :include. I understand the behavior of this better, but are there any disadvantages to this (other than the unnecessary reading of all the columns in the subscriptions table)?
Lastly, I could also retrieve the query again using the record IDs by calling User.find_all_by_id(User.active.map(&:id)), but I find this to be more of a workaround rather than a possible solution since it generates an extra SQL query.
Are there any other possible solutions? What would be the preferred solution in this situation? I've read the answer given in the previous StackOverflow question about this, but it doesn't seem to give specific guidance of what would be considered correct.
Thanks in advance!
I believe that it would be customary and acceptable in this case to use :include instead of :join. I think that :join is only used in rare specialized circumstances, whereas :include is pretty common.
If you're not going to be updating all of the active users, then it's probably wise to add an additional named scope or find condition to further narrow down which users you're loading so that you're not loading extra users & subscriptions unnecessarily. For instance...
User.active.some_further_limiting_scope(:with_an_argument)
#or
User.active.find(:all, :conditions => {:etc => 'etc'})
If you decide that you still want to use the :join, and are only going to update a small percentage of the loaded users, then it's probably best to reload just the user you want to update right before doing so. Such as...
readonly_users = User.active
# insert some other code that picks out a particular user to update
User.find(readonly_users[#index].id).update_attributes(:etc => 'etc')
If you really do need to load all active users, and you want to stick with the :join, and you will likely be updating most or all of the users, then your idea to reload them with an array of IDs is probably your best choice.
#no need to do find_all_by_id in this case. A simple find() is sufficient.
writable_users_without_subscriptions = User.find(Users.active.map(&:id))
I hope that helps. I'm curious which option you go with, or if you found another solution more appropriate for your scenario.
I think the best solution is to use .join as you have already and do a separate find()
One crucial difference of using :include is that it uses outer join while :join uses an inner join! So using :include may solve the read-only problem, but the result might be wrong!
I ran across this same issue and was not comfortable using :readonly => false
As a result I did an explicit select namely :select => 'users.*' and felt that it seemed like less of a hack.
You could consider doing the following:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :select => 'users.*', :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
Regarding your sub-question: so am I not properly understanding the purpose of automatically setting the read-only flag on :joins?
I believe the answer is: With a joins query, you're getting back a single record with the User + Subscription table attributes. If you tried to update one of the attributes (say "subscription_num") in the Subscription table instead of the User table, the update statement to the User table wouldn't be able to find subscription_num and would crash. So the join-scopes are read-only by default to prevent that from happening.
Reference:
1) http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html

Some security and error handling issues?

With the help of some here, I'm using this code to generate tokens purchased from my app. It loops through depending on how many tokens are ordered, calls a method called create_trackable_token which just generates a unique string to identify the token. I have two questions about this.
1) How can I rewrite it so the particular columns are not vulnerable to mass-assignment? Right now, if I use 'attr_accessible` on this model, I have to expose the three attributes in this method because it's assigning them at once. I'd prefer to not do that. The method is already protected, so it can't be called by an end user.
2) What's the best way of handling errors? The tokens have to be unique, and right now, I'm not sure what would happen if the create_trackable_token method generates a string that's already in use. Does ActiveRecord take care of that or do I need to write some error handling into the method?
protected
def create_trackables
return unless self.success
order = Order.find(order_id) #you shouldn't need this line if it has_one :order
1.upto(order.total_tokens) do
Tracker.create!(
:user_id => order.user_id,
:token => Tracker.create_trackable_token,
:order_id => order_id
)
end
end
To handle uniqueness, use a validation on the column, see http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
When creating the token, there are many things to consider, such as how hard do you want it to be to guess (or else do you authenticate a token against something else?) ... you could use hashes, such ass MD5, random number, or you could go all out and make a GUID. In any case, you could look for the token before returning from create_trackable_token.
Tracker.where(:token => generated_token).exists?
If you want to keep it mass assignment safe you have to do this in a few steps:
t = Tracker.create(
:user_id => order.user_id,
:order_id => order_id
)
t.token = Tracker.create_trackable_token
t.save!

How to best secure records in Rails?

I have some records in my database which only the owning users, or administrators are allowed to view/edit etc.
What is the best way to secure these records on an application level so that only these people can see these records? I could do this manually with conditions, but I would need to ensure I use the same conditions on every single find().
Any ideas?
Scoping a finder to the current_user is useful in most instances, but does not account for a user being an administrator and having access to objects to which it is not directly associated.
Create a named scope within the model to restrict the selection to records owned by the user or any if the specified user is an administrator. The User model must implement a method called "is_admin?" that returns true if the User is considered an admin.
This way you can call:
my_widgets = Widget.accessible_by(user).find(:all, :conditions => ["created_at > ?", 1.day.ago.to_s(:db)])
class Widget
named_scope :accessible_by, lambda {|u|
conditions = nil
unless u.is_admin?
conditions = ["widgets.user_id = :user_id", {:user_id => u.id}]
end
return {:conditions => conditions}
}
end
I find the best way is to avoid finders on the actual model like this...
SecretRecord.find(:id, :conditions => 'user = ?', current_user.id)
and instead use the collections off the user object
class User
has_many :secret_records
end
current_user.secret_records.find(id)
This automatically scopes the select to those secret records that belong to the current user.
I'm assuming that you have a variable called current_user of type User provided by your authentication system (such as restful_authentication)
Put a filter in front of all controllers that can be used to access this information
current_user.admin? or params[:person_id].to_i == current_user.person.id
If this condition is not met redirect them somewhere else

Eager loading not working in Rails 2.2.2

I'm working with models analogous to the following:
class Owner < ActiveRecord::Base
has_many :owned
end
class Owned < ActiveRecord::Base
belongs_to :owner
end
You can presume that owned_id and owner_id are in the right places. The trouble is that, in a controller for a different mvc chain in the app,
#owner = Owned.find_by_id(owned_id, :include => :owner)
doesn't work. I get the owner_id, column, naturally, but can't then do
#owned.owner # is just nil
What gives? I mean, I could do the assignment directly before passing the result on to the view:
#owned.owner = Owner.find_by_id(#owned.owner_id)
but that just seems silly. Come on, embarrass me. What's the obvious thing that I've missed? This works in other places in my app, but I can't spot the differences. Are there some common traps? Anything helps.
Thank you
I just keep winning. The corresponding 'Owner' object had been deleted from the owners table.
The funny thing is, before I created an account, I had tons of karma on my cookie-based identity. Then my cookies became corrupted, and I can't ask anything but stupid questions anymore, and my karma sits at 1. Oh well.
Reputation on StackOverflow is not cookie based. You may have to log in again or something.
Your question seems to imply that you have an owned_id field in the owner table. You don't need that and should remove it.
You just need an owner_id integer field in the owned table.
You can access your records and relationships in a number of ways. First let's start by accessing the owner record first.
owner = Owner.find(owner_id)
owned = owner.owned # this is an array since you a 'has_many' relationship
Normally you'd want to access the owned records in the following way:
for owned in owner.owned
puts owned.name # or access any other attributes
end
If you would like to access the owned records first you could do the following:
#owned = Owned.find(:all, :conditions => [ "owner_id = ?", owner_id ])
# #owned is an array so you need to iterate through it
for owned in #owned
puts owned.owner.name # or access any other attribute from the owner
end
Once you've got these queries working fine you can worry about eager loading by adding :include in your find statements. Note that this can be of interest for optimization but not necessary from the get go.
I hope this helps.

Resources