Environment - Rails 4.2, Ruby 2.3
Trying to play with scopes and created one that will return true or false for the user.superuser of the current_user, from inside the non-User Model/Controller. I am getting the expected output when current_user.superuser = true but false just goes out left field.
Breakdown:
appliance.rb (model)
scope :superuser, ->(user) { user.superuser ? true : false }
appliance_controller.rb
def list
#appliances = Appliance.all.order(:idtag)
#users = User.all
end
list.html.haml
%h5
= "Is the user a superuser? #{Appliance.superuser(current_user).to_s}"
Rails console for when querying a user that has the superuser attribute set to true
irb(main):006:0* current_user = User.first User Load ... #<User id: 3, superuser: true>
irb(main):007:0> Appliance.superuser(current_user)
=> true
Rails console for when querying a user that has the superuser attribute set to false
irb(main):008:0> current_user = User.last User Load # User id: 6, superuser: false>
irb(main):010:0* Appliance.superuser(current_user)
Appliance Load (4.1ms) SELECT "appliances".* FROM "appliances"
=>#ActiveRecord::Relation [#Appliance id:1, ... updated_at: "...">, #Appliance id:2, ... updated_at: "...">]>
Basically it's dumping Appliance.all and returns an ActiveRecord_relation instead of false. Can anyone explain why this is happening?
As per the rails api :
Scope Adds a class method for retrieving and querying objects. The method is intended to return an ActiveRecord::Relation object, which is composable with other scopes. If it returns nil or false, an all scope is returned instead.
So this is not unexpected, rather a exact expected behavior. your scope returns false, it applies all scope and dumps all of the Appliance records, which are activerecord relation objects, as stated in api.
Related
I'm trying to setup an attribute that isn't saved to the database but I can't work out how to change it and read the new value.
class User < ApplicationRecord
attribute :online, :boolean, default: false
end
in Rails Console:
User.first.online = true
=> true
User.first.online
=> false
I'm running Ruby-on-rails 5.2.4.1 and ruby 2.4.1
https://api.rubyonrails.org/v5.2.4.1/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
The line:
User.first
Creates an instance for the first user each time you call it.
User.first.equal?(User.first) #=> false
# ^^^^^^
# Equality — At the Object level, returns true only if obj
# and other are the same object.
You're setting the online attribute of a different instance than the one you're reading from (although they represent the same record). Store the user in a variable. That way you're working with the same instance for both the set and get call.
user = User.first
user.online = true
user.online #=> true
I have a User table for my app which contains the list of all users. This table has a Boolean field named active.
I have this code to fetch the user:
existing_user = User.where("LOWER(email) = ?", auth_hash['info']['email'].downcase)
And this is what I get when I do an existing_user.inspect:
User Load (1.9ms) SELECT "users".* FROM "users" WHERE (LOWER(email) = 'biswanath.chandramouli#gmail.com')
#<ActiveRecord::Relation [#<User id: 4, name: "Biswanath Chandramouli", email: "Biswanath.Chandramouli#gmail.com", last_updated_by: "", admin: true, active: true, last_updated_on: nil, created_at: "2018-10-30 08:14:59", updated_at: "2018-10-30 08:14:59"
As you can see, existing_user has the property active available as shown above.
But this code fails:
if(!existing_user.active?)
The above call throws this error:
undefined method `active?' for #<User::ActiveRecord_Relation:0x00007f0a58b2c500> Did you mean? acts_like?
When existing_user.inspect shows active: true, why does the above call existing_user.active fail? Pls help!
I think you should use if(!existing_user.first.active?). This will work in your case. Where clause returns you an array, not an object. In your case, existing_user is an array not an object.
This answer is off-topic but could save you a lot:
Every time you call this existing_user = User.where("LOWER(email) = ?", auth_hash['info']['email'].downcase), it's going to downcase all the emails in the table and look for the correct one.
I would suggest to downcase the email before saving the user and add an index on it;
before_save { self.email = self.email.downcase }
and then get the user:
user = User.where(email: auth_hash['info']['email'].downcase).first
Try this method and you'll see a big difference in the data retrieval (which is now 1.9ms)
I am manually creating objects in the rails console using Model.new(<attributes here>). Is there an easy way to list out which attributes a model will require me to include in order for the .save call to succeed?
I am running rails 4.2.3
You can get an array of validators using Model.validators. You'll have to parse this in some way to extract those validations for presence, something like:
presence_validated_attributes = Model.validators.map do |validator|
validator.attributes if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
end.compact.flatten
I found a simpler way to accomplish the same thing:
When you do a failed create you can check the error message on the object.
# app/models/price.rb
class Price < ActiveRecord::Base
validates_presence_of :value
end
# in console
p = Price.new()
=> #<Price id: nil, created_at: nil, updated_at: nil, value: nil>
p.save
=> false
p.errors.messages
=> {:value=>["can't be blank"]}
In case you the mandatory attributes with error messages
book = Book.new
book.valid?
book.errors.messages
In case you just want the name of attributes without an error message
book = Book.new
book.valid?
book.errors.messages.keys
Suppose there are users records in the database. And we decided to add validation in model. Model:
class User < ActiveRecord::Base
validates_format_of :name, with: /\A[^\d]*\z/, allow_blank: true
before_validation :delete_digits_from_name
def delete_digits_from_name
self.name = name.gsub!(/\d/, '')
end
end
Scenario 1 in console:
User.create(name: 'Username 15')
User.last
=> #<User id: 14154, name: "Username"
And it's ok. But there are old record (created before adding validation) and.. scenario 2:
user = User.first
=> #<User id: 1, name: "Username 15"
user.save
=> true
user
=> #<User id: 1, name: "Username"
user.reload
=> #<User id: 1, name: "Username 15"
But why?? Why changes not saved?
The gsub! in delete_digits_from_name changes the name in place, so Rails thinks name is the same thing it loaded from the DB. It's the same object, even though you've changed its value. Rails does this to optimize away DB updates when no data has changed, and in-place editing confuses it.
Switching to self.name = self.name.gsub(/\d/, '') (no !) assigns a new String that Rails will recognize as dirty and needing saving.
You can also add name_will_change! after your gsub! to tell Rails the attribute needs saving.
I'm testing chats between users in my app. I'm using RSpec and FactoryGirl
The test that's not passing:
it "creates a chat if one does not exist" do
bob = create(:user, username: "bob")
dan = create(:user, username: "dan")
new_chat = Chat.create(user_id: #dan.id, chatted_user_id: bob.id)
expect(Chat.where("chatted_user_id = ?", bob.id).first).to equal(new_chat)
end
The failure message says:
Failure/Error: expect(Chat.where("chatted_user_id = ?", bob.id).first).to equal(new_chat)
expected #<Chat:70120833243920> => #<Chat id: 2, user_id: 2, chatted_user_id: 3>
got #<Chat:70120833276240> => #<Chat id: 2, user_id: 2, chatted_user_id: 3>
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.
Why is my query returning a different object id?
equal checks object identity. The objects you are testing are two objects (instances) referencing the same record, but they are actually different objects from a Ruby virtual machine point of view.
You should use
expect(Chat.where("chatted_user_id = ?", bob.id).first).to eq(new_chat)
To better understand the problem, look at the following example
2.0.0-p353 :001 > "foo".object_id
=> 70117320944040
2.0.0-p353 :002 > "foo".object_id
=> 70117320962820
Here I'm creating two identical strings. They are identical, but not equal because they are actually two different objects.
2.0.0-p353 :008 > "foo" == "foo"
=> true
2.0.0-p353 :009 > "foo".equal? "foo"
=> false
That's the same issue affecting your test. equal checks if two objects are actually the same at the object_id level. But what you really want to know is if they are the same record.