Rails 4 enum - how to test equality? - ruby-on-rails

I am using Rails 4.1 and Ruby 2.1.1
I have a line in my user model:
enum role: [:user, :admin, :team_admin, :domain_admin, :super_admin]
In my controller I want to only do something if my user is a :domain_admin and I use the following test:
if #user.role == :domain_admin
The test returns false when #user.role (in the console) returns :domain_admin. So the value is set properly, but I must be misunderstanding testing equality of it, or enum's do not work as I previously thought. I assumed from reading the documentation that they were a thin layer over (small) ints.
Could anyone tell me how I test equality for :domain_admin, and also how do I test >= :domain_admin?
Many thanks.

#user.domain_admin? # return true if :domain_admin
instead:
#user.role == :domain_admin
use:
#user.role == "domain_admin"
Some test:
=> User.roles
=> {"user"=>0, "staff"=>1, "admin"=>2}
=> u = User.last
=> u.role
=> "user"
=> u.role == "user" # <-- this
=> true
=> User.roles.each_pair { |x, _| puts u.role == x }
=> true
=> false
=> false

Related

Rails 4: Use Operators within Loops

I am struggling with checking the param user_id. How can I change that from => to something similar this operator: !=
And fortunately how can I do the same with <= and >=
- #jobsforyou.where(:is_finished => false, :is_active => true, :user_id => current_user, :sort <= 2).limit(10).each do |job|
The code-example gives an error.
For not you can directly do this
#jobsforyou.where.not(user_id: current_user)
For <= and >= you can use something similar to this
#jobsforyou.where("sort < ?", 3)
=> in your example is not equal or bigger than. It is the syntax of a hash. To write more complex queries you will need to use another syntax - for example the pure string syntax:
#jobsforyou.where(:is_finished => false, :is_active => true, :user_id => current_user
.where('sort <= 2')
.limit(10)
Read more about how to build queries in Ruby on Rails in the Rails Guides.

conditional scope based on either two fields being true but not both

I am needing to create a scope that checks for either two fields on the model being true, however it shouldn't include records where both are true, only ones where either of them are. I hope that makes sense.
I am using Rails 3.2 and Mongo 3. Can any recommend a way to achieve this?
My first attempt has been
scope :with_training_complete, where(
:volunteer_training_completed => true
).or(:face_to_face_training_attended => true)
but that brings back only records where both are true.
any help would be much appreciated.
You are suppose to use ^ for this.
This will explain what it does:
irb(main):002:0* a
=> 1
irb(main):003:0> a ==1
=> true
irb(main):004:0> b=2;
irb(main):005:0* (a==1) && (b==2)
=> true
irb(main):006:0> (a==1) ^ (b==2)
=> false
irb(main):007:0> (a==1) ^ (b==3)
=> true
irb(main):008:0>
I managed to get what I want from VonD's comment. This led me to create:
scope :with_training_complete, where( :$or => [ { :video_training_completed => true }, { :face_to_face_training_completed => true } ])

With a has_many relationship, in the returned collection, include? does not recognize equality

In one of my models I have defined equality to also work with strings and symbols. A role is equal to another role (or string or symbol), if its name attribute is the same:
class Role
def == other
other_name = case other
when Role then other.name
when String, Symbol then other.to_s
end
name == other_name
end
end
The equality checking works correct:
role = Role.create name: 'admin'
role == 'admin' # => true
role == :admin # => true
But when I use the Role model in a has_many relationship, in the collection I get, include? does not recognized this equality:
user = User.create
user.roles << role
User.roles.include? role # => true
User.roles.include? 'admin' # => false
User.roles.include? :admin # => false
In order to make this work, I have to explicitly convert this to an array:
User.roles.to_a.include? 'admin' # => true
User.roles.to_a.include? :admin # => true
So apparently Rails overrides the include? method in the array returned by user.roles. This sucks and is contrary to rubys specification of Enumerable#include? (which explicitly states, that "Equailty is tested using =="). This is not true for the array I get from user.roles. == is never even called.
Where is this modified behavior of include? specified?
Is there another way to test for inclusion that I missed? Or do I have to use to_a or an actual instance of Role everytime?
You are not implementing your equality operator correctly. It should be:
def == other
other_name = case other
when Role then other.name
when String, Symbol then other.to_s
end
name == other_name
end

save! method for referenced attributes in mongoid

I use Rails 3.0.6 with mongoID 2.0.2. Recently I encountered an issue with save! method when overriding setter (I am trying to create my own nested attributes).
So here is the model:
class FeedItem
include Mongoid::Document
has_many :audio_refs
def audio_refs=(attributes_array, binding)
attributes_array.each do |attributes|
if attributes[:audio_track][:id]
self.audio_refs.build(:audio_track => AudioTrack.find(attributes[:audio_track][:id]))
elsif attributes[:audio_track][:file]
self.audio_refs.build(:audio_track => AudioTrack.new(:user_id => attributes[:audio_track][:user_id], :file => attributes[:audio_track][:file]))
end
end
if !binding
self.save!
end
end
AudioRef model (which is just buffer between audio_tracks and feed_items) is:
class AudioRef
include Mongoid::Document
belongs_to :feed_item
belongs_to :audio_track
end
And AudioTrack:
class AudioTrack
include Mongoid::Document
has_many :audio_refs
mount_uploader :file, AudioUploader
end
So here is the spec for the FeedItem model which doesn`t work:
it "Should create audio_track and add audio_ref" do
#audio_track = Fabricate(:audio_track, :user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3"))
#feed_item= FeedItem.new(
:user => #author,
:message => {:body => Faker::Lorem.sentence(4)},
:audio_refs => [
{:audio_track => {:id => #audio_track.id}},
{:audio_track => {:user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3")}}
]
)
#feed_item.save!
#feed_item.reload
#feed_item.audio_refs.length.should be(2)
end
As you can see, the reason I am overriding audio_refs= method is that FeedItem can be created from existing AudioTracks (when there is params[:audio_track][:id]) or from uploaded file (params[:audio_track][:file]).
The problem is that #feed_item.audio_refs.length == 0 when I run this spec, i.e. audio_refs are not saved. Could you please help me with that?
Some investigation:
1) binding param is "true" by default (this means we are in building mode)
I found a solution to my problem but I didnt understand why save method doesnt work and didn`t make my code work. So first of all let me describe my investigations about the problem. After audio_refs= is called an array of audio_refs is created BUT in any audio_ref is no feed_item_id. Probably it is because the feed_item is not saved by the moment.
So the solution is quite simple - Virtual Attributes. To understand them watch corresponding railscasts
So my solution is to create audio_refs by means of callback "after_save"
I slightly changed my models:
In FeedItem.rb I added
attr_writer :audio_tracks #feed_item operates with audio_tracks array
after_save :assign_audio #method to be called on callback
def assign_audio
if #audio_tracks
#audio_tracks.each do |attributes|
if attributes[:id]
self.audio_refs << AudioRef.new(:audio_track => AudioTrack.find(attributes[:id]))
elsif attributes[:file]
self.audio_refs << AudioRef.new(:audio_track => AudioTrack.new(:user_id => attributes[:user_id], :file => attributes[:file]))
end
end
end
end
And the spec is now:
it "Should create audio_track and add audio_ref" do
#audio_track = Fabricate(:audio_track, :user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3"))
#feed_item= FeedItem.new(
:user => #author,
:message => {:body => Faker::Lorem.sentence(4)},
:audio_tracks => [
{:id => #audio_track.id},
{:user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3")}
]
)
#feed_item.save!
#feed_item.reload
#feed_item.audio_refs.length.should be(2)
end
And it works fine!!! Good luck with your coding)
Check that audio_refs=() is actually being called, by adding debug output of some kind. My feeling is that your FeedItem.new() call doesn't use the audio_refs=() setter.
Here's the source code of the ActiveRecord::Base#initialize method, taken from APIdock:
# File activerecord/lib/active_record/base.rb, line 1396
def initialize(attributes = nil)
#attributes = attributes_from_column_definition
#attributes_cache = {}
#new_record = true
#readonly = false
#destroyed = false
#marked_for_destruction = false
#previously_changed = {}
#changed_attributes = {}
ensure_proper_type
populate_with_current_scope_attributes
self.attributes = attributes unless attributes.nil?
result = yield self if block_given?
_run_initialize_callbacks
result
end
I don't currently have an environment to test this, but it looks like it's setting the attributes hash directly without going through each attribute's setter. If that's the case, you'll need to call your setter manually.
Actually, I think the fact you're not getting an exception for the number of arguments (binding not set) proves that your setter isn't being called.

Trying to master Ruby. How can I optimize this method?

I'm learning new tricks all the time and I'm always on the lookout for better ideas.
I have this rather ugly method. How would you clean it up?
def self.likesit(user_id, params)
game_id = params[:game_id]
videolink_id = params[:videolink_id]
like_type = params[:like_type]
return false if like_type.nil?
if like_type == "videolink"
liked = Like.where(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink").first unless videolink_id.nil?
elsif like_type == "game"
liked = Like.where(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game").first unless game_id.nil?
end
if liked.present?
liked.amount = 1
liked.save
return true
else # not voted on before...create Like record
if like_type == "videolink"
Like.create(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink", :amount => 1)
elsif like_type == "game"
Like.create(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game", :amount => 1)
end
return true
end
return false
end
I would do something like:
class User < ActiveRecord::Base
has_many :likes, :dependent => :destroy
def likes_the(obj)
like = likes.find_or_initialize_by_likeable_type_and_likeable_id(obj.class.name, obj.id)
like.amount += 1
like.save
end
end
User.first.likes_the(VideoLink.first)
First, I think its wrong to deal with the "params" hash on the model level. To me its a red flag when you pass the entire params hash to a model. Thats in the scope of your controllers, your models should have no knowledge of the structure of your params hash, imo.
Second, I think its always cleaner to use objects when possible instead of class methods. What you are doing deals with an object, no reason to perform this on the class level. And finding the objects should be trivial in your controllers. After all this is the purpose of the controllers. To glue everything together.
Finally, eliminate all of the "return false" and "return true" madness. The save method takes care of that. The last "return false" in your method will never be called, because the if else clause above prevents it. In my opinion you should rarely be calling "return" in ruby, since ruby always returns the last evaluated line. In only use return if its at the very top of the method to handle an exception.
Hope this helps.
I'm not sure what the rest of your code looks like but you might consider this as a replacement:
def self.likesit(user_id, params)
return false unless params[:like_type]
query = {:user_id => user_id,
:likeable_id => eval("params[:#{params[:like_type]}_id]"),
:likeable_type => params[:like_type].capitalize}
if (liked = Like.where(query).first).present?
liked.amount = 1
liked.save
else # not voted on before...create Like record
Like.create(query.merge({:amount => 1}))
end
end
I assume liked.save and Like.create return true if they are succesful, otherwise nil is returned. And what about the unless game_id.nil? ? Do you really need that? If it's nil, it's nil and saved as nil. But you might as well check in your data model for nil's. (validations or something)

Resources