Ruby Delete multiple objects by association - ruby

My app needs to destroy all of the teams that have fewer than two members.
This method seems to be working, but I'm hoping to "rubify" it to one line if possible.
#consultancy.teams.reverse.each do |team|
team.destroy if team.users.count < 2
end
I'm trying to do something more like the following, but I'm getting an error for the reject! method.
#consultancy.teams.reject!{|x| x.users.count < 2}
NoMethodError: undefined method `reject!' for #<Team::ActiveRecord_Associations_CollectionProxy:0x00000003f72850>
.delete_all also throws an error.
#consultancy.teams.select{|x| x.users.count < 2}.delete_all
NoMethodError: undefined method `delete_all' for #<Array:0x0000000b72dcf0>
Thank you in advance for any insight.

select acts on arrays, but delete_all is an active record relation method.
You could do
#consultancy.teams.select{|x| x.users.count < 2}.map(&:delete)
But a better way might be to do the count select as part of the query.
#consultancy.teams.joins(:users).group('teams.id').having('count(users.id) < 2').destroy_all

Related

Access value from a Netsuite hash, Enumerator

Hi I am trying to extract a value from a Netsuite hash inside custom fields, and some others, which typically look like this - `
"custbody_delivery_ticket_number"=>
{
"script_id"=>"custbody_delivery_ticket_number",
"internal_id"=>"2701",
"type"=>"platformCore:DateCustomFieldRef",
"attributes"=> {
"value"=>"123abc"
}
}` and want the value of it inside of attributes.
Have tried many different ways, but one in particular -
delivery_ticket_number: "#{netsuite_sales_orders.custom_field_list.custom_fields.select['custbody_nef_meter_ticket_number']['attributes']['value']}",
throws error for class Enumerator, NoMethodError: undefined method `[]' for #Enumerator:0x00005589ec778730 which indicates may be getting close, but doing something wrong.
If anyone has any idea how to get values from these kind of hashes?
(Am told by the system admin that it is the correct custbody identifier)
Many Thanks
Eventually fixed this, grabbing Netsuite custom fields with a select of script_id by name,and map as below:
delivery_date:netsuite_sales_order.custom_fields_list.custom_fields.select { |field| field.script_id == 'custbody_delivery_date' }.map { |field| field.value }.first
First selecting the script_id by unique name, then mapping to the value. Was able to get any custom field like this, preferable as they can move and might not have the same index if use an index to grab them, fetching an incorrect value. This way ensures getting the correct data even if the item is moved up or down in the custom fields list.
Thanks for everyones help!

Fix deprecation warning `Dangerous query method` on `.order`

I have a custom gem which creates a AR query with input that comes from an elasticsearch instance.
# record_ids: are the returned ids of the ES results
# order: is the order of the of the ids that ES returns
search_class.where(search_class.primary_key => record_ids).order(order)
Right now the implementation is that I build the order string directly into the order variable so it looks like this: ["\"positions\".\"id\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC"]
This works fine but throws a deprecation warning which ultimately will not work in rails6.
DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "\"positions\".\"id\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC". Non-attribute arguments will be disallowed in Rails 6.0. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql()
So I tried couple of different approaches but all of them with no success.
order = ["\"positions\".\"id\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC"]
# Does not work since order is an array
.order(Arel.sql(order))
# No errors but only returns an ActiveRecord_Relation
# on .inspect it returns `PG::SyntaxError: ERROR: syntax error at or near "["`
.order(Arel.sql("#{order}"))
# .to_sql: ORDER BY [\"\\\"positions\\\".\\\"id\\\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC\"]"
order = ['fcdc924a-21da-440e-8d20-eec9a71321a7', ...]
# Won't work since its only for integer values
.order("idx(ARRAY#{order}, #{search_class.primary_key})")
# .to_sql ORDER BY idx(ARRAY[\"fcdc924a-21da-440e-8d20-eec9a71321a7\", ...], id)
# Only returns an ActiveRecord_Relation
# on .inspect it returns `PG::InFailedSqlTransaction: ERROR:`
.order("array_position(ARRAY#{order}, #{search_class.primary_key})")
# .to_sql : ORDER BY array_position(ARRAY[\"fcdc924a-21da-440e-8d20-eec9a71321a7\", ...], id)
I am sort of stuck since rails forces attribute arguments in the future and an has no option to opt out of this. Since the order is a code generated array and I have full control of the values I am curious how I can implement this. Maybe someone had this issue before an give some useful insight or idea?
You could try to apply Arel.sql to the elements of the array, that should work, ie
search_class.where(search_class.primary_key => record_ids)
.order(order.map {|i| i.is_a?(String) ? Arel.sql(i) : i})

How do I use the value stored in a variable as a method call?

I have a method that looks like this:
> rating
=> "speed"
Then I have a call that looks like this:
profile.ratings.find_by(user: current_user).speed
What I want to do is pass the value of rating to that call.
But when I do this:
profile.ratings.find_by(user: current_user).rating
It doesn't work, because there is no method called rating on each ratings object.
This is the error I get when I run the above:
Rating Load (4.5ms) SELECT "ratings".* FROM "ratings" WHERE "ratings"."profile_id" = $1 AND "ratings"."user_id" = 7 LIMIT $2 [["profile_id", 12], ["LIMIT", 1]]
NoMethodError: undefined method `rating' for #<Rating:0x007fca0c4fba90>
I would normally do string interpolation, except now I am working on a method call.
How might I do this?
If you're looking to access a property on an ActiveRecord model they provide a simple accessor:
profile.ratings.find_by(user: current_user)[rating]
This is safer than the send method since it's only going to fetch attributes. If you had a method called ban_and_charge_ten_bucks! some hostile user might be able your system into calling that if you call send without checking what you're calling.
You can use the send method
profile.ratings.find_by(user: current_user).send(rating)

Create an object if one is not found

How do I create an object if one is not found? This is the query I was running:
#event_object = #event_entry.event_objects.find_all_by_plantype('dog')
and I was trying this:
#event_object = EventObject.new unless #event_entry.event_objects.find_all_by_plantype('dog')
but that does not seem to work. I know I'm missing something very simple like normal :( Thanks for any help!!! :)
find_all style methods return an array of matching records. That is an empty array if no matching records are found. And an empty is truthy. Which means:
arr = []
if arr
puts 'arr is considered turthy!' # this line will execute
end
Also, the dynamic finder methods (like find_by_whatever) are officially depreacted So you shouldn't be using them.
You probably want something more like:
#event_object = #event_entry.event_objects.where(plantype: 'dog').first || EventObject.new
But you can also configure the event object better, since you obviously want it to belong to #event_entry.
#event_object = #event_entry.event_objects.where(plantype: 'dog').first
#event_object ||= #event_entry.event_objects.build(plantype: dog)
In this last example, we try to find an existing object by getting an array of matching records and asking for the first item. If there are no items, #event_object will be nil.
Then we use the ||= operator that says "assign the value on the right if this is currently set to a falsy value". And nil is falsy. So if it's nil we can build the object form the association it should belong to. And we can preset it's attributes while we are at it.
Why not use built in query methods like find_or_create_by or find_or_initialize_by
#event_object = #event_entry.event_objects.find_or_create_by(plantype:'dog')
This will find an #event_entry.event_object with plantype = 'dog' if one does not exist it will then create one instead.
find_or_initialize_by is probably more what you want as it will leave #event_object in an unsaved state with just the association and plantype set
#event_object = #event_entry.event_objects.find_or_initialize_by(plantype:'dog')
This assumes you are looking for a single event_object as it will return the first one it finds with plantype = 'dog'. If more than 1 event_object can have the plantype ='dog' within the #event_entry scope then this might not be the best solution but it seems to fit with your description.

evaluate string to modify data model in ruby

I seem to be running into a problem with Rails 3 and I can't seem to figure it out.
Here's what I am trying to do:
att1 = "column"
att2 = "1"
final_column = "#{att1}_#{att2}"
obj.final_column = 4
====> Error
-----> NoMethodError: undefined method `final_column=' for class....
If I do this it works though:
obj.column1=4
What can I do to my final_column to make it work?
Thanks!
You want to do this:
obj.send("#{final_column}=", 4)
If you want to respect the private/protected visibliy, use public_send instead of send.

Resources