0

I have two arrays that represent records from a User table.

@server = [{ id: 1, name: "john" }, { id: 2, name: "Sarah" }, { id: 3, name: "George" }]
@client = [{ id: 1, name: "john" }, { id: 2, name: "Sarah" }]

I want to run a function that checks one array against the other and deletes George's record because they no longer exist in @client

At the moment I have this rather long looking method, which works but definitely doesn't look optimal.

@server.each do |item|
  if @client.select{ |obj| obj[:id] == item.id }.length < 1
    User.find(item.id).delete
  end
end

What is the optimal method for this problem?

3 Answers 3

2

You're essentially asking to find all the common elements, and that's super easy to do with Array#&:

a = %w[ john sarah george ]
b = %w[ sarah john ringo ]

a & b
# => ["john", "sarah"]

That finds the union of the two sets, or in other words, strips out entries not present in both.

To find out the ones that need to be removed, you can also use Array#- to subtract them out:

to_delete = a - b
Sign up to request clarification or add additional context in comments.

2 Comments

Doesn't to_delete = a - b produce the same result?
@3limin4t0r I think you're right! Edited the answer. Thanks for spotting that.
2

I can think of two different ways to do it.

1) Get the diff by using Array#- method and directly delete those entries from the database like so;

ids_to_delete = (server - client).map { |entry| entry[:id] }

User.where(id: ids_to_delete).delete_all

2) Second way is just let the database engine to this job for you;

server_ids = server.map { |entry| entry[:id] }
client_ids = client.map { |entry| entry[:id] }

User.where(id: server_ids).where.not(id: client_ids).delete_all

I would rather prefer the first option as the second query might end up with sending a big query to database and also testing the first solution would be much simpler with just unit testing.

1 Comment

Note that the first option compares all attributes of the hash, not only the id. For the scenario given this doesn't matter, since all other attributes are the same.
1

I would first map both collections to their ids:

server_user_ids = @server.map { |user| user[:id] }
client_user_ids = @client.map { |user| user[:id] }

You can then remove all users with:

User.where(id: server_user_ids - client_user_ids).delete_all

Note that both delete and delete_all will not trigger callbacks. If you do want to trigger callbacks use destroy or destroy_all instead.


If @server and @client can hold large collections I would suggest using sets instead. They have faster lookup time, but decrease readability a bit.

server_user_ids = @server.map { |user| user[:id] }.to_set
client_user_ids = @client.map { |user| user[:id] }.to_set

User.where(id: server_user_ids - client_user_ids).delete_all

I'm not sure if where works with sets. If not, change the above into [*server_user_ids - client_user_ids] which spreads the resulting set into an array. You could also call to_a on the resulting set. (server_user_ids - client_user_ids).to_a

1 Comment

Thanks for this answer. My question was slightly unclear in that the User objects could also contain different amounts of data so mapping to the user ID's was the best solution for me!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.