NoMethodError in RailsAdmin::MainController#edit - ruby-on-rails-5

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.

Related

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.

Getting size of users group not working in Ruby

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

In a Rails ActiveRecord model, is using after_initialize callbacks a very bad idea?

Let's suppose we have this model
class Account < ActiveRecord::Base
after_initialize :set_name
def set_name
self.name = ‘My Account’
end
end
Now I want run a query that returns only some attributes of the model but not all of them, in particular is not returning the "name" attribute that it is used in after_initialize callback
Account.group(:name).select("count(*), id").first
And then this execution raises the following error because the set_name callback uses an attribute that has not been "loaded" or selected into the records returned by the query.
ActiveModel::MissingAttributeError: missing attribute: name
Fortunately for some particular cases I can execute the same sql query without using the Account model at all to get the desired result
sql = Account.group(:name).select("count(*), id").to_sql
ActiveRecord::Base.connection.execute(sql).first
=> #<Mysql2::Result:0x00000106eddbc0>
But the point is, what if I want to get Account objects instead of a Mysql2::Result one? Should the .select method return "complete" objects with all their attributes (e.g. filling the missing columns with Nil's)? Or is just a very bad idea to use after_initialize callbacks for our ActiveRecord models? Of course we can also add some code in the callback to check if the property exists or not but, in my opinion, this is unnatural or sounds weird working in an OO language.
Most uses of after_initialize can be (and SHOULD be) replaced with defaults on the corresponding database columns. If you're setting the property to a constant value, you may want to look into this as an alternative.
EDIT: if the value isn't constant, a call to has_attribute?(:name) will guard against this error - ActiveModel::MissingAttributeError occurs after deploying and then goes away after a while
No, it is not a bad idea, in fact I use it very often at work. The valid use case for this would be when you want code to run before you try and do anything with the object. Here is a breakdown of some of the filters offered.
# Before you intend to do anything with the object
after_initialize
# Before you intend to save the object
before_save
# After you've saved the object
after_save
# Before you save a new record
before_create
# After you create a new object
after_create

Rails 3 - how do you use methods like where and sum on proxy_targets in association extension methods?

in my app, 2 peope can be friends via a friendships model and they can create records of how much money they lent to the other friend.
I want to create something like this
#friendship.receipts.balance_for(#user) which will return how much #user owes to or is owed by their friend in this friendship. i.e. if it returns -50 they owe $50 or if its positive they are owed $50
Id also like to be able further scope this for use in live filtering (by dates, tags etc) i.e. would like this
#friendship.receipts.where(:tag => "groceries).balance_for(#user)
My first attempt was to create an association extension on the friendship model
class Friendship < ...
has_many :receipts do
def balance_for(user)
user_total_spent = proxy_target.sum(:value, :payer_id => user.id)
friend_total_spent = proxy_target.sum(:value, :payer_id => friend.id)
return user_balance = user_total_spent - friend_total_spent
end
end
end
but unfortunately the method sum cant be used on proxy_target because it returns a plain array and not the Activerecord::Relation class sum expects.
Whats a good workaround this??? Id like to create a method that can be called on scoped results.
You might want to try simply calling sum without an explicit receiver. I'm looking for the solution to a similar problem, and found what looks like the answer to your question here: http://withoutscope.com/2008/8/22/don-t-use-proxy_target-in-ar-association-extensions
Unfortunately, it doesn't solve my problem. I'm trying to figure out how to extend models retrieved through my association extension with a module where the module name is a member of the proxy's owner.

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.