I have a model called Stem. I need a 'thumbs up' feature, so I have created a second model called Thumb, which consists of stem_id and user_id.
I'm also using the restful authentication plugin for user credentials.
I have the 'thumbs up' button working, which adds a row to the thumbs table fine, but I'd like to be able to check if the currently logged in user has already given a thumbs up to this particular stem.
I tried adding this to the Stem model:
def thumbed
Thumb.count_by_sql ["SELECT COUNT(*) FROM thumbs WHERE user_id = ? AND stem_id = ?", current_user.id, self.id ]
end
The problem here is that the stem model has no access to the current_user variable the the controllers have.
Is there a way I can get access to this property, or alternatively, is there another way I could go about checking this? I was hoping to get this as a property in the model because the stems are passed over to a Flex app using RubyAMF.
Thanks!
Your controller knows the current_user, and your Stem model probably shouldn't. You can, however clean up your code and avoid hard-wiring SQL with a named_scope and pass the user into that.
#thumb.rb
named_scope :for_user_id, lambda {|id| {:conditions => {:user_id => id}}}
#stem.rb
def thumbed_by_user(user)
self.thumbs.for_user_id(user.id).count > 0
end
# some controller
stem = some_method_that_gets_a_stem
is_already_thumbed = stem.thumbed_by_user(current_user)
Can you pass the current user to thumbed? Where are you calling it from?
BTW, you could try to simplify all of this using associations. I'm not 100% sure I understand what you're trying to do, but it sounds like you have the following...
class Stem
has_many :thumbs
end
class User
has_many :thumbs
end
class Thumb
belongs_to :user
belongs_to :stem
end
Then you can use find though associations to get at your thumbs without resorting to direct SQL. Check out this RailsCast: http://railscasts.com/episodes/3-find-through-association
What ended up working for me first was something like this:
I added these methods to my stem model:
def thumbed_by? usr_id
Thumb.count(:conditions => {:user_id => usr_id, :stem_id => self.id}) > 0
end
def owned_by? usr_id
self.id == usr_id
end
I also added this:
attr_accessor :thumbed, owned
Then in my show action where these were needed, I added the following lines:
#stem.thumbed = #stem.thumbed_by? current_user.id
#stem.owned = #stem.owned_by? current_user.id
This works exactly how I would like (the Flex app is already interpreting it as properly), but is there a way I could shorten this?
Related
I have a scope method defined in my Child's model profile.rb as follows
scope :fees_to, -> (fees_to) { where("fees_to <= ?", "#{fees_to}") }
And in the parent which is tutor.rb there is
has_one :profile, dependent: :destroy
Now in rails console i can do Profile.all.fees_to(10) for example and its valid. But how can i do call the fees_to method through the parent Tutor?
Basically now i am able to filter through my profiles in the index view of it. But what i would like to do is to filter through the tutor index view based on values from the child.
All help and advice would be greatly appreciated! Thank you
This makes no sense with a has_one. You cannot "have one" and still apply a scope to it. Scopes work on many records, to apply additional conditions and narrow down the result set, not specific records.
You're effectively trying to add a where(fees_to < ?) condition to a specific record, which obviously makes no sense.
If you want to use a scope via an association, it needs to be a has_many.
In profile.rb:
You can create a function like:
def fees_to n
Profile.fees_to(n) # your scope is called here
end
Then in rails console :
tutor = Tutor.first
tutor.profile.fees_to(10)
it works like a filter. If profile's fees_to is > n, you will get [] (array empty)
UPDATED
bad => #tutor = Tutor.all
good => #tutor = Tutor.includes(:profile).all # with eager loading, avoid n+1 query.
may be you want make loop like that:
#tutor.each do |tutor|
tutor.profile.fees_to(10)
end
I defined the following in my tutor.rb
def self.fees_search(n)
#profile = Profile.fees_to(n)
if #profile.empty?
return Tutor.none
else
#profile.each do |y|
y.tutor
end
end
end
Not sure if its clunky or if there are redundancies, but it works perfectly fine in rails console. I can do Tutor.fees_search(10) and it renders all the respective Tutors accordingly.
Cheers
I'm working on implementing a tagging system and I'm having problem querying for tagged objects with a scope.
For example, I would like to find all the user's items with a certain tag. With a class method I can currently find all the objects:
def self.tagged_with(name)
Tag.find_by_name(name).items
end
However, this has a problem. If I were to do something like: current_user.items.tagged_with(name) won't this existing method return ALL the items and not just items owned by the current_user? I suppose this is a simply querying issue but I can't figure out how to change a class method into something called on a collection. I have tried going the opposite way, to get a the collection through the tags, something like... tag.items.where(:user_id => current_user.id) but in this case, it's a many-to-many relationship and I haven't been able to get on thumb on this either.
What's the proper way to restrict a query like this?
Create an association on your User class that points to your Tag class.
class User < ActiveRecord::Base
has_many :tags
end
Then you can do:
current_user.tags.where(...)
If you don't already have an association in place, you'll need to create a migration to have the tags table reference your users table with a foreign key.
I think this will help you:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
So, basically you can define your method tagged_with directly into the association!
This example is took from the documentations ActiveRecord::Associations
I have two models one of them is a User, and another Comments. Comments belong to User.
class User < ActiveRecord::Base
act_as_paranoid
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
When I do user.delete in my controller I get the expected result of the deleted_at column being set and the record being hidden.
My problem is the Comment associations for the user are set to null. So now on the site it shows no user owning the comment. I'd like the comment to still show the user's name not be "None" or "Anonymous" etc.
Looking at the source on github https://github.com/goncalossilva/rails3_acts_as_paranoid/blob/rails3.2/lib/acts_as_paranoid/core.rb it seems to call run_callbacks which in turn results in Rails 3 falling back to Nullify default for associations.
In my case I just want the user account to be closed off when deleted. Not showing up in queries anymore so that Authlogic will deny them and the User index page won't show them. But still allowing everything a user owns to still be owned by them (since they may come back, etc.).
Is there a better way to do this then acts_as_paranoid?
Rather then go to the trouble of overriding the destroy method I created a close method that simply sets closed_at to a timestamp. If you set default scope to something like:
default_scope { where("closed_at IS NULL") }
Then the model won't show up to any queries including User.All. You can delete the scope to get a full query essentially I took these ideas from act_as_paranoid but much more simplified. The problem is that then even though the Comments still have user_id set, the default scope runs with any association load. So say
c = Comment.first
c.user
That will output nil if user_id is a closed account. In my case the easiest solusion was to remove default scoping and modify my Authlogic function to:
def self.find_by_username_or_email(login)
u = User.find(:first, :conditions => ["lower(username) = ?", login.downcase]) || User.find_by_email(login)
return u unless u.closed_at
end
This way closed accounts can't login. Anywhere I list out users in my views I used a hide_closed scope.
Not sure if this was the best most elegant solution. But for my purposes it works.
I use Rails 3 with MongoMapper.
I want to add some records to the result of has many association.
For example, user has_many posts
class User
include MongoMapper::Document
many :posts
end
By default it will show only posts which belongs to the user, but if he/she specify special option in query (or in the user's settings menu, say show-commented=true), then I also need to add posts where user left any comments. So I think to override posts method
def posts
super + (show_commented_posts ? commented_posts : [])
end
But of course it doesn't work. How can I correctly override this method using mongo_mapper? Or is there any better approach for that problem?
Overriding methods on mongomapper is a very bad idea, you should try to refrain from doing it as it creates a lot of problems that are hard to trace back (I've been burned before by this).
Instead, you should consider using a scope such as
class Post
scope :related_to_user, lambda {|user| where('$or' => [ {user_id: user.id}, {'comments.user_id' => user.id}]) }
end
Then you can call
Post.related_to_user(current_user)
I am trying to implement specific object (row) authorisation using cancan, I want it to work in a way that a user can only make a change(update/edit) to a Record if he/she has the role for that specific Record. after consulting the cancan docs I tried doing the following:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Record do |record|
user.can_edit(record)
end
end
end
class User
has_many :assignments
has_many :roles_as_editor, :through => :assignments, :class_name => "Role", :source => :role, :conditions => {:edit => true}
def rec_as_editor
self.roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
end
def can_edit(rec)
rec_as_editor.include?(rec)
end
end
The can_edit method takes in a Record object and ensures that a User has the role necessary to make a change to it by returning true or false. this method is tested and works correctly so the problem seems to be with the CanCan code because when i try editing a Record that the user dosent hold the role for it still allows me to make changes to the Record, anyone know why this wont work?
If you require any further information please let me know through a comment.
Thank You
Are you authorizing the resource in the controller?
you should have load_and_authorize_resource in your controller
or
def edit
#critical_process = CriticalProcess.find(params[:id])
#this here is what you use
authorize! :edit, #critical_process
end
in your edit method inside the critical process controller.
I personally prefer to keep this logic completely separate from models so that I don't have to dig into model code to find authorization issues. In other words, user.can_edit checks for authorization which is what the ability file is supposed to be in charge of. Shouldn't matter though... in this case I think you might have a problem inside the can_edit method. I have used code that looks nearly identical to yours without problems many times like this:
can :manage, Record do |record|
user.has_role?(:record_manager)
end
I suggest including your code for can_edit or use the debugger to see what value gets returned from can_edit.
I think the problem comes from the way you query for the records that are supposed to have the user as an editor.
I copy/pasted your code and built the other associations from scratch.
And testing it in the console it works as expected when I use it:
>> u = User.last
>> a = Ability.new(u)
>> a.can :edit, Role.last
false
The only thing I changed is the query for the records: it seemed to look for a record that owns the role (your Role has a record_id) but then looks for the same key under cp_secondary_id.
I think something is wrong in your query, but what depends on your schema and associations:
roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
as I understood your code we are traversing associations like this:
User=>Assignment<=Role(boolean edit flag)<=Record