I have a collection that contains a class like:
locations = Location.all
class Location < ActiveRecord::Base
end
The location class has a property: code
I wan to remove an item from the collection if code == "unused".
How many different ways can I do this in ruby?
I am currently doing this:
locations = Location.all.select { |l| l.code != "unused" }
This works great but just wondering what other ways I could do this just for learning purposes (if there big performance advantages in another way that would be good to know also).
Update
Please ignore the fact that I am loading my collection initially from the database, that wasn't the point. I want to learn how to remove things in-memory not simple where clauses :)
You can simply fetch records from your database what you need:
Rails 4 onwards:
locations = Location.where.not(code: "unused")
Before Rails 4:
locations = Location.where("code != ?", "unused")
If you have a collection and you want to reject some items from it, then you can try this:
locations.reject! {|location| location.code != "unused"}
You are doing this the wrong way. In your case, you are retrieving all records from DB and getting an array of records. Then you are looking for records you need in the array. Instead, you should get the records directly from DB:
Location.where("code != 'unused'")
# or in Rails 4 and latest
Location.where.not(code: "unused")
If you need to remove records from DB, you can do it like this:
Location.where.not(code: "unused").destroy_all
If you just want to know what is the best way to remove elements from an existing array, I think you are on the right track. Besides select there are reject, reject!, delete_if methods. You can learn more about them in the documentation http://ruby-doc.org/core-2.3.1/Array.html
There is a related post that might give more information: Ruby .reject! vs .delete_if
Related
I'm not sure if this is just a lacking of the Rails language, or if I am searching all the wrong things here on Stack Overflow, but I cannot find out how to add an attribute to each record in an array.
Here is an example of what I'm trying to do:
#news_stories.each do |individual_news_story|
#user_for_record = User.where(:id => individual_news_story[:user_id]).pluck('name', 'profile_image_url');
individual_news_story.attributes(:author_name) = #user_for_record[0][0]
individual_news_story.attributes(:author_avatar) = #user_for_record[0][1]
end
Any ideas?
If the NewsStory model (or whatever its name is) has a belongs_to relationship to User, then you don't have to do any of this. You can access the attributes of the associated User directly:
#news_stories.each do |news_story|
news_story.user.name # gives you the name of the associated user
news_story.user.profile_image_url # same for the avatar
end
To avoid an N+1 query, you can preload the associated user record for every news story at once by using includes in the NewsStory query:
NewsStory.includes(:user)... # rest of the query
If you do this, you won't need the #user_for_record query — Rails will do the heavy lifting for you, and you could even see a performance improvement, thanks to not issuing a separate pluck query for every single news story in the collection.
If you need to have those extra attributes there regardless:
You can select them as extra attributes in your NewsStory query:
NewsStory.
includes(:user).
joins(:user).
select([
NewsStory.arel_table[Arel.star],
User.arel_table[:name].as("author_name"),
User.arel_table[:profile_image_url].as("author_avatar"),
]).
where(...) # rest of the query
It looks like you're trying to cache the name and avatar of the user on the NewsStory model, in which case, what you want is this:
#news_stories.each do |individual_news_story|
user_for_record = User.find(individual_news_story.user_id)
individual_news_story.author_name = user_for_record.name
individual_news_story.author_avatar = user_for_record.profile_image_url
end
A couple of notes.
I've used find instead of where. find returns a single record identified by it's primary key (id); where returns an array of records. There are definitely more efficient ways to do this -- eager-loading, for one -- but since you're just starting out, I think it's more important to learn the basics before you dig into the advanced stuff to make things more performant.
I've gotten rid of the pluck call, because here again, you're just learning and pluck is a performance optimization useful when you're working with large amounts of data, and if that's what you're doing then activerecord has a batch api you should look into.
I've changed #user_for_record to user_for_record. The # denote instance variables in ruby. Instance variables are shared and accessible from any instance method in an instance of a class. In this case, all you need is a local variable.
I am new to Ruby for one project only - I need to join two tables with aws dynamodb. Basically the equivalent of sql left join. But since dynamodb apparently doesn't support I need to make it happen at the array level it seems.
Currently I am querying the one just fine, but I need to bring in this other table, but I'm having a heck of a time finding a simple example for ruby with rails without using ActiveRecord (to avoid causing an overhaul on pre-existing code).
client = Aws::DynamoDB::Client.new
response = client.scan(table_name: 'db_current')
#items = response.items
fake output to protect the innocent
db_current
{"machine_id"=>"pc-123435", "type_id"=>"t-56778"}
db_type
{"description"=>"Dell 5 Dev Computer", "Name"=>"Dell", "type_id"=>"t-56778"}
I thought I might have to make two:
client = Aws::DynamoDB::Client.new
db_c = client.scan(table_name: 'db_current')
#c_items = db_c.items
client = Aws::DynamoDB::Client.new
db_t = client.scan(table_name: 'db_type')
#t_items = db_c.joins(db_t['type_id']) <=== then merge them
here.
where I'll ultimately display description/name/machine_id
But sadly no luck.
I'm looking for suggestions. I'd prefer to keep it simple to really
understand (It might sound unreasonable, I don't want to pull in ActiveRecord just yet unless I'll be owning this project going forward).
I ended up doing it this way. There is probably a more elegant solution for those that are familiar with Ruby... that I am not.
basically for each of the items in the first hash array (table), I use the ID from that one to filter on the item for the 2nd hash array. Merging them in the process. then appending to a final destination which I'll use for my UI.
#c_by_id = Array.new
#b_items.each do |item|
pjoin = #c_items.first {|h| h['b_id'] == item['b_id']}
newjoin = item.merge(pjoin)
#c_by_id.append(newjoin)
end
How can i remove the last element from an ActiveRecord_Relation in rails?
e.g. if I set:
#drivers = Driver.all
I can add a another Driver object called #new_driver to #drivers by doing:
#drivers << #new_driver
But how can I remove an object from #drivers?
The delete method doesn't seem to work, i.e.
#drivers.delete(0)
You can use the reject! method, this will remove the object from the collection without affecting the db
for example:
driver_to_delete = #driver.first # you need the object that you want removed
#drivers.reject!{|driver| driver == driver_to_delete}
Very late too, but I arrived here looking for a fast answer and finished by thinking by myself ;)
Just to clarify about the different answers and the Rails 6.1 comment on accepted answer:
The OP wanted to remove one entry from a query, but NOT remove it from database, so any answer with delete or destroy is just wrong (this WILL delete data from your database !!).
In Ruby (and therefore Rails) convention, shebang methods (ending with !) tend to alter the given parameter. So reject! would imply modifying the source list ... but an ActiveRecord_Relation is basically just a query, NOT an array of entries !
So you'd have 2 options:
Write your query differently to specifically say you don't want some id:
#drivers.where.not(id: #driver_to_remove) # This still is an ActiveRecord_Relation
Use reject (NO shebang) on your query to transform it into an Array and "manually" remove the entry you don't want:
#drivers.reject{ |driver| driver == #driver_to_remove}
# The `reject` forces the execution of the query in DB and returns an Array)
On a performance point of view, I would personally recommend the first solution as it would be just a little more complex against the DB where the latter implies looping on the whole (eventually large) array.
Late to the question, but just had the same issue and hope this helps someone else.
reject!did not work for ActiveRecord_Relation in Rails 4.2
drop(1) was the solution
In this case #drivers.drop(0) would work to drop the first element of the relation
Since its an array of objects, have you tried to write something like #drivers.delete(#new_driver) or #drivers.delete(id: #new_driver.id) ?
This is the documentation you need:
#group.avatars << Avatar.new
#group.avatars.delete(#group.avatars.last)
--
.destroy
The problem you've got is you're trying to use collection methods on a non-collection object. You'll need to use the .destroy ActiveRecord method to get rid of the record from the database (and consequently the collection):
#drivers = Driver.all
#drivers.last.destroy
--
Scope
.delete will remove the record from the DB
If you want to pull specific elements from the db to populate the #drivers object, you'll need to use a scope:
#app/models/driver.rb
Class Driver < ActiveRecord::Base
scope :your_scope, -> { where column: "value" }
end
This will allow you to call:
#app/controllers/drivers_controller.rb
def index
#drivers = Driver.your_scope
end
I think you're getting the MVC programming pattern confused - data manipulation is meant to happen in the model, not the controller
As stated above, reject! doesn't work in Rails 4.2, but delete does, so #drivers.delete(#new_driver) works, and more generally:
#drivers.delete(Driver.where(your condition))
I have Board model. Board can be subscribed to other boards (as a feed).
Lets say I have board tree like this:
http://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Binary_tree.svg/200px-Binary_tree.svg.png
So:
Board.find(2).feeds are boards 5 and 7
Board.find(7).feeds are boards 2 and 6 etc.
I want to write method all_feeds which returns all feeds from all levels for certain board. For example:
Board.find(7).all_feeds would output array of boards with ids: 2,6,5,11
I started with something like:
def all_feeds
if feeds.empty?
return
else
feeds.each {|feed| feed.all_feeds}
return feeds
end
end
Probably have to add this return feeds to some global array, but not sure how should I do this.
Thanks for help.
ps. this is not always a binary tree, you can have more than 2 feeds.
I guess that what you want could be achieved with:
def all_feeds
unless feeds.empty?
feeds + feeds.map(&:all_feeds).flatten.compact
end
end
Array#flatten makes the result one-dimensional, while Array#compact removes the nil components.
For an explanation of the map(&:all_feeds) part, you can refer to this SO answer :)
Looks like it's working for below code:
def all_feeds
if feeds.empty?
self
else
[self]+feeds.map(&:all_feeds)
end
end
if it is allowed to use gems ancestry gem will help do the trick
Board.find(7).descendants
in this case it will be definitely one request to db without any recursion which is better for performance
you can implement ancestry idea without gem (or in top of it):
add ancestry field to your model
fill it correctly when you build your tree (for nested nodes with ids 2 and 6 it will be 2/7, with ids 5 and 11 - 2/7/6 )
and then just take it from db with like 2/% query
I have a simple find statement as such:
m = MyModel.find(1, :include => :my_children)
With m.mychildren being an Array; is there anyway to find a particular record from within the array without having to iterate over the entire thing. If I do mychildren.find(1), a new DB query is issues, which doesn't make sense, since they are all loaded already
It looks like there's a little Rails magic going on here. Where Enumerable#find is being overridden by ActiveRecord::Base#find on methods created for associations.
On the upside Enumerable#find is aliased to Enumerable#detect.
Unfortunately Enumerable#find/Enumerable#detect have significantly different syntax from ActiveRecord::Base#find.
So you can't just do mychildren.find(1), instead you've got to do mychildren.detect{|c| c.id == 1} if you want to avoid hitting the database again. You may also want to consider extending Array for a more DRY way of doing this.
class Array
def id_find id
self.detect{|element| element.id == id}
end
end
I'm not quite sure what your asking, but have you tried select:
m.mychildren.select{ |child| child == <<some_statement>> }
This won't hit the database assuming you've used the :include option as you stated in your question.
Alternatively, if you know the number of the child you want, you should be able to just use
m.mychildren[1]