Getting size of users group not working in Ruby - sql

I am gathering all of the users who have administrative privileges with #admins = User.find_by(admin: true), but when I try and get the number of admins with puts #admins.size, I get the error NoMethodError: undefined method 'size' for #<User:0x00000009b78988>. I expect to get just 1. Any idea what's going wrong?

You're expecting it to return a list of all the matching Users, but it doesn't -- find_by "finds the first record matching the specified conditions."
You can use User.where(admin: true) instead, and that should work as you intend, returning an Array of all the Users where admin is true.

as mentioned, find_by will only return the first instance of record matching the given condition.
Since you want a list of all the admin, use where.
Also, you can turn this into a scope in the User model as follow:
scope :admins, -> { where(admin: true) }
And then call:
User.admins # gets a list of all the admins
User.admins.size # or User.admins.count to return the number of admins you have.

find_by will return only the first record matching the conditions. What you want here is probably where
#admins = User.where(admin: true)
#admins.size

Related

NoMethodError in RailsAdmin::MainController#edit

I have deposit and wallet models in my rails admin. I am using wallet in deposit model as:
wal=Wallet.where(user_id: self.user_id)
wal.balance = wal.balance + self.amount
wal.save!
Error:
NoMethodError in RailsAdmin::MainController#edit
The result of
puts wal.inspect
is
<ActiveRecord::Relation [#<Wallet id: 1, balance: 200, user_id: 22, created_at: "2019-03-20 04:57:45", updated_at: "2019-03-20 06:43:32">]>
I found the solution, but I don't think its a proper approach
wal=Wallet.where(user_id: self.user_id)
wal[0].balance = wal[0].balance + self.amount
wal[0].save!
Any help would be appreciated. Thanks in advance.
.where always returns a relation and not an object. The relation is basically an array of objects. Regardless if your search returns one,zero or many objects.
So the clean way of doing this is similar to your approach. You just select the object in the index 0 position of the relation array.
wal = Wallet.where(user_id: self.user_id).first
Another approach could be that you add a relation to the user model. Assumption would be that you make sure User always has just exactly one wallet and you have the user available.
# model
has_one :wallet
# controller
user = User.find(self.user_id)
wal = user.wallet
Find always returns one object (if it exists) since there can only be one.
Also an approach could be .find_by. Its similar to find but works for other object fields. For this you have to make sure that there is only one wallet per user since find_by returns just the first object even if there are multiple objects that match the query.
Wallet.find_by(user_id: self.user_id)
The last 2 examples have the advantage that they return a ActiveRecord::RecordNotFound when there is no match. The .where returns only an empty relation array.

Difference between update and update_attributes

In Rails 5, what is the difference between update and update_attributes methods. I'm seeing the following results for both the methods
Returns true/false
Checking for active record validation
Call backs are triggered
and also regarding update method a new thing was introduced in active record relation. I'm not able to understand it. What is the difference?
Moreover are we using update_attributes in Rails 5. It's not there in active record documentation.
I'm confused with all update methods. I need clarity
As of Rails 4.0.2, #update returns false if the update failed. Before Rails 4.0.2, #update returned the object that got updated. The main difference therefore was the return value. After this change, #update_attributes is just an alias of #update. It seems there are talks to deprecate #update_attributes in Rails 6 which is not released yet.
https://github.com/rails/rails/pull/31998
https://github.com/rails/rails/commit/5645149d3a27054450bd1130ff5715504638a5f5
From the rails 5 files it seems to me update can be used to update multiple objects(array of records) but update_attributes only work on single records otherwise both are same
From rails core files for update_attributes:
Updates a single attribute and saves the record.
This is especially useful for boolean flags on existing records. Also note that
Validation is skipped.
\Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
This method raises an ActiveRecord::ActiveRecordError if the
attribute is marked as readonly.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
save(validate: false)
end
For Update
Updates an object (or multiple objects) and saves it to the database, if validations pass.
The resulting object is returned whether the object was saved successfully to the database or not.
==== Parameters
+id+ - This should be the id or an array of ids to be updated.
+attributes+ - This should be a hash of attributes or an array of hashes.
==== Examples
# Updates one record
Person.update(15, user_name: "Samuel", group: "expert")
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
# Updates multiple records from the result of a relation
people = Person.where(group: "expert")
people.update(group: "masters")
Note: Updating a large number of records will run an UPDATE
query for each record, which may cause a performance issue.
When running callbacks is not needed for each record update,
it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
for updating all records in a single query.
def update(id, attributes)
if id.is_a?(Array)
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
object.update(attributes[idx])
}
else
if ActiveRecord::Base === id
raise ArgumentError,
"You are passing an instance of ActiveRecord::Base to `update`. " \
"Please pass the id of the object by calling `.id`."
end
object = find(id)
object.update(attributes)
object
end
end
When we are working with update_column that time update is done on the database level there is no any contact with the rails ORM so whatever logic we have implemented like callbacks and validations all will be waste and wont be useful as this is going to be bypassed.
I found this article explained really well in just 30 seconds.
.update
Use update when you want to return false, for example in an if/else:
if record.update(params)
display_success
else
react_to_problem
end
.update!
Use update! when you want an error (for example: to avoid erroring silently, which could be very bad if an error was unexpected and you needed to know about it to fix it!):
record.update!(params) # raises is invalid
'update' respects the validation rules on model, while 'update_attributes' ignores validations.

How to get an object from a list of objects (ASP.NET MVC4)

I know how to "translate" this into code:
if the Cancellation.User.Id is the same as currentUser.Id
if (cancellation.User.Id != currentUser.Id){...}
But what if my Cancellation object contains not one User but a List<User> Users?
How do I check if the Cancellation object contains a User whose Id is the same as currentUser.Id?
LINQ will solve this quite nicely.
if (cancellation.Users.Any(u => u.ID == currentUser.Id)){...}
Note that this is true if there is a matching user in the list, which is what you said in your last sentence, but seems counter to the code snippet you provided, which is checking if the ID does not match. If you want to trigger this code if none of the users match, just put a ! before the whole lot (not in the lambda).

How do I perform a search in Rails3?

I'm aware that I can search queries by calling where() on a model as follows:
Post.where(:title => 'My First Post')
However, what if I don't know if the user wants to filter out the search parameters?
For example, I have a search form that has an optional title field. If the title is filled, the form should search for a title. If it is not, however, the form should just return all fields.
I tried doing something along the lines of
search = Post.all
if params[:title].present?
search.where(:title => params[:title])
end
However, Rails immidiately returns the result of the search when I call Post.all and I cannot further add conditions/
How do I do this?
Thanks!
.all is a 'finisher' method for arel and causes the query to actually be called (same goes for .each and .first). So, if you want to be able to keep building up the scope conditionally, .all should be the last thing called, if you want to call it at all.
Given that there really isn't much in the way of relations to chain here, it would seem that a simple one-liner with a ternary operator might do:
#posts = params[:title].present? ? Post.where(:title => params[:title]) : Post.all
... because when there is no :where clause scope, you couldn't later chain it to :all, anyway.

Rails3: Cascading Select Writer's Block

I have a big, flat table:
id
product_id
attribute1
attribute2
attribute3
attribute4
Here is how I want users to get to products:
See a list of unique values for attribute1.
Clicking one of those gets you a list of unique values for attribute2.
Clicking one of those gets you a list of unique values for attribute3.
Clicking one of those gets you a list of unique values for attribute4.
Clicking one of those shows you the relevant products.
I have been coding Rails for about 4 years now. I just can't unthink my current approach to this problem.
I have major writer's block. Seems like such an easy problem. But I either code it with 4 different "step" methods in my controller, or I try to write one "search" method that attempts to divine the last level you selected, and all the previous values that you selected.
Both are major YUCK and I keep deleting my work.
What is the most elegant way to do this?
Here is a solution that may be an option. Just off the top of my head and not tested (so there is probably a bit more elegant solution). You could use chained scopes in your model:
class Product < ActiveRecord::Base
scope :with_capacity, lambda { |*args| args.first.nil? ? nil : where(:capacity=>args.first) }
scope :with_weight, lambda { |*args| args.first.nil? ? nil : where(:weight=>args.first) }
scope :with_color, lambda { |*args| args.first.nil? ? nil : where(:color=>args.first) }
scope :with_manufacturer, lambda { |*args| args.first.nil? ? nil : where(:manufacturer=>args.first) }
self.available_attributes(products,attribute)
products.collect{|product| product.send(attribute)}.uniq
end
end
The code above will give you a scope for each attribute. If you pass a parameter to the scope, then it will give you the products with that attribute value. If the argument is nil, then the scope will return the full set (I think ;-). You could keep track of the attributes they are drilling down in in the session with 2 variables (page_attribute and page_attribute_value) in your controller. Then you call the entire chain to get your list of products (if you want to use them on the page). Next you can get the attribute values by passing in the set of products and the attribute name to Product.available_attributes. Note that this method (Product.available_attributes) is a total hack and would be inefficient for a large set of data, so you may want to make this another scope and use :select=>"DISTINCT(your_attribute)" or something more database efficient instead of iterating thru the full set of products as I did in the hack method.
class ProductsController < ApplicationController
def show
session[params[:page_attribute].to_sym] = params[:page_attribute_value]
#products = Product.all.with_capacity(session[:capacity]).with_weight(session[:weight]).with_color(session[:color]).with_manufacturer(session[:manufacturer])
#attr_values = Product.available_attributes(#products,params[:page_attribute])
end
end
Again, I want to warn you that I did not test this code, so its totally possible that some of the syntax is incorrect, but hopefully this will give you a starting point. Holla if you have any questions about my (psuedo) code.