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

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 } ])

Related

Is there a benefit to Rails' ".present?" method?

In Ruby on Rails, is there a difference between:
if obj
# ...
end
and:
if obj.present?
# ...
end
It seems they do the same thing and not using .present? would help keep the line of code shorter and potentially cleaner. I understand that .present? is the opposite of blank? but should we always be using it when trying to determine if an object is "truthy" or not?
Is there a performance difference of some kind?
The #present? method does a bit more in that it also returns false if the string was a real but empty string (i.e. "")
Which is useful as your forms might return empty strings instead of nils.
You can also use #presence which is a useful way of returning the value only if the value is #present?
name = params[:name].presence || 'ardavis'
The above wouldn't work if params[:name] was an empty string and you didn't use #presence
They don't do the same thing at all.
In Ruby everything except nil and false are truthy. This is amazingly sane compared to the type casting schenigans in other popular dynamic languages.
irb(main):003:0> !!""
(irb):3: warning: string literal in condition
=> true
irb(main):004:0> !!0
=> true
irb(main):005:0> !![]
=> true
irb(main):006:0> !!{}
=> true
irb(main):007:0> !!Object.new
=> true
irb(main):008:0> !!nil
=> false
irb(main):009:0> !!false
=> false
present? and presence are a part of ActiveSupport which can be used to test for nil and false but are actually more useful when dealing with user input:
irb(main):010:0> "".present?
=> false
irb(main):011:0> [].present?
=> false
irb(main):012:0> {}.present?
=> false
present? and presence are widely overused by Rails beginners that don't bother to learn Ruby first. Just use implicit truth checks (if foo) or foo.nil? if you just want to check if an argument is sent or not or if a variable is set.
And while .present? can be used on ActiveRecord collections but there are more idiomatically correct choices such as any? and none?.
If you are working with string only checking if the attribute or object exists will return true, but present method will return false.
Here are some examples:
# Double negation return the boolean value
!!""
=> true
"".present?
=> false
" ".present?
=> false
[].present?
=> false
nil.present?
=> false
true.present?
=> true
false.present?
=> false
{}.present?
=> false
person = {:firstName => "John", :lastName => "Doe"}
person.present?
=> true
5.present?
=> true

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.

Rails 4 enum - how to test equality?

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

State_machine previous state

I am using rails state_machine gem. I have one model Lead with a status field. Can I change the status to a previous status from where it is changed with using the state_machine? I have a contact check box, when user will click it it will be lead.made_contact! but when they are unchecked it should be reverted. Will state_machine help? Thanks.
If you're modeling interaction with a checkbox, it seems to me the only transition you can expect is to toggle it. Then, the status message can be a virtual attribute determined by the value of this state machine.
A state machine is probably overkill for this scenario, but assuming you're simplifying the problem for the sake of the question, here's another way to go about it.
class Lead
state_machine :contact_state, :initial => :unchecked, :namespace => "contact" do
event :toggle do
transition :unchecked => :checked, :unless => lambda {|obj| obj.contact_state }
transition :checked => :unchecked, :if => lambda {|obj| obj.contact_state }
end
state :checked, :value => true
state :unchecked, :value => false
end
def status
contact_state ? "lead.made_contact!" : ""
end
end
That code produces the following behavior. You can, of course, change the namespacing or attribute to your liking.
irb(main):001:0> lead = Lead.new
=> #<Lead:0x007fccec42c898 #contact_state=false>
irb(main):002:0> lead.contact_state
=> false
irb(main):003:0> lead.status
=> ""
irb(main):004:0> lead.toggle_contact
=> true
irb(main):005:0> lead.contact_state
=> true
irb(main):006:0> lead.status
=> "lead.made_contact!"
irb(main):007:0> lead.toggle_contact
=> true
irb(main):008:0> lead.status
=> ""
You must define steps explicitly like:
event :next do
transition from: :without_contact, to: :with_contact
end
event :back do
transition from: :with_contact, to: :without_contact
end
This way you can do .next and .previous
What makes the power of state machines is that you have to let the object handle itself.
You've to think hard about authorized and illicit transitions, then you just use events.
Having very explicit transition names is often the sign of a non abstracted state machine. (look at spree, the basically use one event: next, abstraction is total)

How can I pass multiple attributes to find_or_create_by in Rails 3?

I want to use find_or_create_by, but this statement does NOT work. It does not "find" or "create" with the other attributes.
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property.id, :value => d[descname])
There seems to be very little, or no, information on the use of dynamic finders in Rails 3. "and"-ing these together gives me a an unknown method error.
UPDATE:
Originally I couldn't get the following to work. Please assume I'm not an idiot and "product" is an instance of Product AR model.
product.product_properties.find_or_create_by_property_id_and_value(:property_id => 1, :value => "X")
The error methods was:
no such keys: property_id, value
I couldn't figure that out. Only this morning did I find the reference to passing the values like this instead:
product.product_properties.find_or_create_by_property_id_and_value(1, "X")
And voilá, it works fine. I would have expected a hash to work in the same situation but I guess not.
So I guess you get a down vote if you miss something on the internet?
If you want to search by multiple attributes, you can use "and" to append them. For example:
productproperty = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(:product_id => product.id, :property_id => property.id, :value => d[descname])
There is one minor catch to be aware of. It will always return the object you've specified, even if that object can't be saved due to validation errors. So make sure you check to see if the returned object has an id (or is_valid?). Don't assume its in the database.
Alternatively, you can use the 'bang' version of the method to raise an error if the object cannot be saved:
http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by-bang
This applies to Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Base.html:
With single query parameter:
productproperty = ProductProperty.find_or_create_by_product_id(product.id) { |u| u.property_id => property_id, u.value => d[descname] } )
or extended with multiple parameters:
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property_id, :value => d[descname]) { |u| u.property_id => property_id, u.value => d[descname] } )
Would work with:
conditions = { :product_id => product.id,
:property_id => property.id,
:value => d[descname] }
pp = ProductProperty.find(:first, :conditions => conditions) || ProductProperty.create(conditions)
In Rails 4, you can use find_or_create_by(attr1: 1, attr2: 2) to find or create by multiple attributes.
You can also do something like:
User.create_with(
password: 'secret',
password_confirmation: 'secret',
confirmation_date: DateTime.now
).find_or_create_by(
email: 'admin#domain.com',
admin: true
)
If you need to create the user with some attributes, but cannot search by those attributes.
You could also use where(...).first_or_create - ActiveRecord::Relation#first_or_create.
product_property_attrs = { product_id: product.id,
property_id: property.id,
value: d[descname] }
product_property = ProductProperty.where(product_property_attrs).first_or_create
I've found in Rails 3.1 you do not need to pass the attributes in as a hash. You just pass the values themselves.
ProductProperty.find_or_create_by_product_id_and_property_id_and_value(
product.id, property.id, d[descname])

Resources