I am using acts as voteable to implement a web poll. With two choices it is simple to determine whether a user has voted or not.
#user.likes #comment1
#user.up_votes #comment2
# user has not voted on #comment3
#user.voted_for? #comment1 # => true
#user.voted_for? #comment2 # => true
#user.voted_for? #comment3 # => false
#user.voted_as_when_voted_for #comment1 # => true, user liked it
#user.voted_as_when_voted_for #comment2 # => false, user didnt like it
#user.voted_as_when_voted_for #comment3 # => nil, user has yet to vote
https://github.com/ryanto/acts_as_votable
I need to has custom multiple choices and have implemented it based upon this:
How do I setup a multi-option voting system using acts-as-votable?
The item above states you can check if a user has voted with voted_for? however this does include scoped items:
Poll.first.vote_by voter: User.first, vote_scope: 'blue'
User.first.voted_for? Poll.first #false
User.first.voted_for? Poll.first, :vote_scope => 'blue' #true
My question is what is the best way to determine if a user has voted when using scopes? Do I need to loop through and check each scope individually for each record?
Edit 1
Currently I have the following Poll instance method:
def has_voted?(user)
['red', 'green', 'blue', 'white'].each do |option|
if user.voted_for? self, :vote_scope => option
return true
end
end
return false
end
Poll.first.has_voted?(User.first)
It looks like you should be able to call Poll.first.votes_for and get a list of the votes that have been cast:
p.votes_for
=> #<ActiveRecord::Associations::CollectionProxy [#<ActsAsVotable::Vote id: 1,
votable_type: "Poll", votable_id: 1, voter_type: "User", voter_id: 1, vote_flag: true,
vote_scope: "blue", vote_weight: 1,
created_at: "2017-11-05 22:12:52", updated_at: "2017-11-05 22:12:52">]>
With that list you should be able to check if any of the voter_ids matches the User you are looking for:
p.votes_for.any? { |v| v.voter_id == u.id }
=> true
Related
I need to get all attributes of an object. I know there's a method attributes, but it doesn't return attributes which are nil.
For example:
class User
include Mongoid::Document
field :name
field :email
field :age
end
u = User.new(email: 'foo#bar.com', name: 'foo')
u.save
u.attributes # {'email' => 'foo#bar.com', 'name' => 'foo'}
I need u.attributes to return {'email' => 'foo#bar.com', 'name' => 'foo' 'age' => nil}
There's a method as_json which does what I want, but it's a lot slower. Speed is very important.
I found a quick solution
self.attribute_names.map { |name| [name, self[name]] }.to_h
It does all I want =)
I want to use acts-as-votable to implement a voting system where I can supply multiple custom options - e.g. 5 buttons ('blue', 'red', 'green', 'grey', 'white').
I want my users to be able to choose only 1 of those colors, but I would like to be able to tally up all the votes (10 - blue, 4 - red, etc.) per item.
I feel like I would use vote-scopes, but I am not quite sure how.
How do I do this with acts-as-votable?
Seem to be pretty straightforward:
https://github.com/ryanto/acts_as_votable#examples-with-scopes
#item.vote_by voter: #user1, vote_scope: 'blue'
#item.vote_by voter: #user2, vote_scope: 'red'
#item.votes_for.size # => 2
#item.find_votes_for(vote_scope: 'blue').size # => 1
#item.find_votes_for(vote_scope: 'red').size # => 1
So you'll need a set of 5 radio buttons (for 5 colors) on your page for the user to select from, and send the selected params to controller where you'll create the vote with selected color.
Then you can check if user voted for this item and disable the future voting for it:
#user.voted_for? #item # => true
Update based on comments
params: {id: 1, scope: 'green'}
#item = Item.find(params[:id])
scope = params[:scope]
if ['red', 'blue', 'green'].include? scope
#item.vote_by voter: current_user, vote_scope: scope
else
# show error message
end
Is there a way to compare two instances of model like
Model.compare_by_name("model1", "model2") which would list the differing column fields
You can use ActiveRecord::Diff if you want a mapping of all the fields that differ and their values.
alice = User.create(:name => 'alice', :email_address => 'alice#example.org')
bob = User.create(:name => 'bob', :email_address => 'bob#example.org')
alice.diff?(bob) # => true
alice.diff(bob) # => {:name => ['alice', 'bob'], :email_address => ['alice#example.org', 'bob#example.org']}
alice.diff({:name => 'eve'}) # => {:name => ['alice', 'eve']}
There is no standard comparator for this. The standard ActiveModel comparator:
Returns true if comparison_object is the same exact object, or comparison_object is of the same type and self has an ID and it is equal to comparison_object.id.
You can write your own by using Hash#diff from activesupport. Something like the following should hopefully get you started:
def Model.compare_by_name(model1, model2)
find_by_name(model1).attributes.diff(find_by_name(model2).attributes)
end
Without using a library or defining a custom method, you can easily get a diff between two models.
For instance,
a = Foo.first
b = Foo.second
a.attributes = b.attributes
a.changes #=> {"id" => [1,2] }
I have a certain requirement where the views have different content based upon the type of user. Lets say I have the index action for the users controller. Then I can use cancan to authorize the action like this
authorize! :index, #users
Further for filtering the content I have another authorization like
if can :view_all,User
Further another authorization like
if can :view_some,User will require another one.
This will result in lots of conditions. Instead of this, I could have used just simple conditions like
If the user is with view_all access show him all
else if the user is with view_some access show him some
else access denied
Cancan requires one extra query, isn't it? I might be using cancan the wrong way. So need some suggestions.
Here is the rough snippet of my ability.rb file
can :index, User do |user1|
role.accesses.include?(Access.where(:name => "access1").first) || role.accesses.include?(Access.where(:name => "access2").first)
end
can :view_all, User do |user1|
role.accesses.include?(Access.where(:name => "access1").first)
end
can :view_some, User do |user1|
role.accesses.include?(Access.where(:name => "access2").first)
end
Cancan requires one extra query?
When combining abilities, cancan will use a single query.
If you look at the specs, eg. spec/cancan/model_adapters/active_record_adapter_spec.rb, you'll find specs like this:
it "should fetch any articles which are published or secret", focus: true do
#ability.can :read, Article, :published => true
#ability.can :read, Article, :secret => true
article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true)
article4 = Article.create!(:published => false, :secret => false)
Article.accessible_by(#ability).should == [article1, article2, article3]
end
And if you turn on SQL logging, you'll see that the query combines the conditions:
Article Load (0.2ms)
SELECT "with_model_articles_95131".*
FROM "with_model_articles_95131"
WHERE (("with_model_articles_95131"."secret" = 't')
OR ("with_model_articles_95131"."published" = 't'))
I have the following:
#permission = #group.permissions.create(
:user_id => #user.id,
:role_id => 2,
:creator_id => current_user.id)
How can I update that to be find_or_create, so that if this record already exists, it's assigned to #permission, and if it doesn't exist, the record is created?
While the accepted answer is correct it's important to note that in Rails 4 this syntax will be changing (and the hash syntax). You should be writing the following:
#permission = Permission.where(
user_id: #user.id,
role_id: 2,
creator_id: current_user.id).first_or_create
Which actually looks much closer to your original method! See the sub-section Deprecated Finders for more details.
Related topic:
find_or_create_by in Rails 3 and updating for creating records
You can extend ActiveRecord with your own update_or_create method (see related topic) and then you can use this
#permission = Permission.update_or_create_by_user_id_and_role_id_and_creator_id(#user.id, 2, current_user.id) do |p|
p.group_id = #group.id
end
Or you can use find_or_create_by... method:
#permission = Permission.find_or_create_by_user_id_and_role_id_and_creator_id(#user.id, 2, current_user.id)
#permission.group = #group
#permission.save
I'm updating questions with versioned answers. Because its important.
Rails 4 (docs)
There are a few ways to "find or create" an object, one is find_or_create_by(args)
Client.find_or_create_by(email: "bambam#flinstones.com", phone: "4255551212")
But the community preferred way is using where
client = Client.where(email: "bambam#flinstones.com", phone: "4255551212").first_or_create
and then you can do something like:
client = Client.where(client_params.slice(:email, :phone)).first_or_create
client.update(client_params)
Rails 3 (docs)
Suppose you want to find a client named ‘Andy’, and if there’s none,
create one and additionally set his locked attribute to false. You can
do so by running:
client = Client.where(:first_name => 'Andy').first_or_create(:locked => false)
# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
Or you wanna try this if you have many fields to fill in:
conditions = { :user_id => #user.id,
:role_id => 2,
:creator_id => current_user.id }
#permission = group.permissions.find(:first, :conditions => conditions) || group.permissions.create(conditions)
see this post:
How can I pass multiple attributes to find_or_create_by in Rails 3?