Reputation: 35
Sample code:
defmodule Foo do
def fast_cars_cache do
fast_cars =
{
%{color: "Red", make: "Mclaren", mileage: 15641.469},
%{color: "Blue", make: "Ferrari", mileage: 120012.481},
%{color: "Red", make: "Ferrari", mileage: 29831.021},
%{color: "Black", make: "Ferrari", mileage: 24030.674},
%{color: "Cobalt", make: "Ferrari", mileage: 412.811},
%{color: "Blue", make: "Koenigsegg", mileage: 250.762},
%{color: "Cobalt", make: "Koenigsegg", mileage: 1297.76},
%{color: "Titanium", make: "Koenigsegg", mileage: 5360.336},
%{color: "Blue", make: "Maserati", mileage: 255.78}
}
if Enum.member?(:ets.all(), :fast_cars_cache) do
:ets.delete(:fast_cars_cache)
:ets.new(:fast_cars_cache, [:duplicate_bag, :public, :named_table])
:ets.insert(:fast_cars_cache, fast_cars)
else
:ets.new(:fast_cars_cache, [:duplicate_bag, :public, :named_table])
:ets.insert(:fast_cars_cache, fast_cars)
end
end
def update_record do
fast_cars_cache()
old_record = :ets.first(:fast_cars_cache)
new_record =
%{color: "Black", make: old_record.make, mileage: 1641.469}
:ets.delete(:fast_cars_cache, {old_record})
|> IO.inspect(label: "Old record deleted")
:ets.insert(:fast_cars_cache, {new_record})
:ets.tab2list(:fast_cars_cache)
end
end
Output:
iex(1)> Foo.update_record
Old record deleted: true
[
{%{color: "Red", make: "Mclaren", mileage: 15641.469},
%{color: "Blue", make: "Ferrari", mileage: 120012.481},
%{color: "Red", make: "Ferrari", mileage: 29831.021},
%{color: "Black", make: "Ferrari", mileage: 24030.674},
%{color: "Cobalt", make: "Ferrari", mileage: 412.811},
%{color: "Blue", make: "Koenigsegg", mileage: 250.762},
%{color: "Cobalt", make: "Koenigsegg", mileage: 1297.76},
%{color: "Titanium", make: "Koenigsegg", mileage: 5360.336},
%{color: "Blue", make: "Maserati", mileage: 255.78}},
{%{color: "Black", make: "Mclaren", mileage: 1641.469}}
]
Observations/Questions:
IO.inspect
, old_record
was deleted yet, as tab2list
reveals, this record still exists. Why is that?old_record
was never deleted, what adjustments does the code need to accomplish this? :ets.select_replace
, if applicable, to perform this update all in one step but I can't make heads or tails of the stipulations for the match specification requirement. It'd be really helpful if someone could disambiguate it with an example or two based on the sample above.As always, thanks so much for your helpful guidance and suggestions :)
Upvotes: 1
Views: 994
Reputation: 41
As Aleksei said, you can also use the :ets.select_replace/2
function.
Here's an example:
:ets.new :t0, [:set, :public, :named_table]
:ets.insert :t0, {{:foo, [:a, :b]}, {:ok, :c}, [ttl: :os.system_time(:seconds) - 1]}
:ets.insert :t0, {{:boo, [:d, :e]}, {:ok, :g}, [ttl: :os.system_time(:seconds) - 1]}
new_opt = {{:new_opt, :this_is_new_opt}}
ms = [{
patter = {key = {:boo, :"$1"}, :"$2", [ttl: :"$3"]},
where = [{:<, :"$3", :os.system_time(:seconds)}],
replace = [{{
key = {{:boo, :"$1"}},
new_value0 = {{:ok, :replaced}},
new_value1 = [
{{:ttl, :os.system_time(:seconds)}},
new_opt
]
}}]
}]
:ets.select_replace :t0, ms
:ets.tab2list :t0
Writing it wholly by hands seems little annoying, instead of it you can hack it just using :ets.fun2ms/1
and then copy past it and adding your variables in the generated matchSpec
.
ms = :ets.fun2ms(fn {{:boo, a}, b, [ttl: c]} = x when c > 1700807468 ->
{{:boo, a}, b, [ttl: c, new: :new]}
end)
Note:
:ets.select_replace/2
in order to replace key, this mean you cannot replace {:boo, [:d, :e]}
;:ets.fun2ms/1
function in an Elixir module, this function is available only in shell;:ets.fun2ms/1
in your codebase;Upvotes: 2
Reputation: 121000
You should clearly distinguish maps and tuples. Map
is a key-value structure. Tuple
is not.
:ets
records are tuples, not maps.
Your initial structure is a tuple of maps. One single tuple, having many maps. It gets inserted as a single element into :ets
, which is clearly visible in your output (check curly braces.)
I would guess you were to insert many elements into the cache.
iex|1 ▶ :ets.new(:fast_cars_cache, [:duplicate_bag, :public, :named_table])
iex|2 ▶ fast_cars = [
...|2 ▶ {"Red", "Mclaren", 15641.469},
...|2 ▶ {"Blue", "Ferrari", 120012.481},
...|2 ▶ {"Red", "Ferrari", 29831.021}
...|2 ▶ ]
iex|3 ▶ :ets.insert(:fast_cars_cache, fast_cars)
:ets.first/1
, as it’s stated in the documentation, returns the first key. :ets.detele/2
deletes a record by key and your code wraps whatever was returned from :ets.first/1
into a one-element tuple, making :ets.delete/2
a no-op no matter what (and yes, :ets.delete/2
always returns true
.)
iex|4 ▶ :ets.first(:fast_cars_cache)
#⇒ "Red"
iex|5 ▶ :ets.delete(:fast_cars_cache, {"Red"}) # NOOP
#⇒ true
iex|6 ▶ :ets.delete(:fast_cars_cache, "Red") # DELETED
#⇒ true
Then if you want to insert a new record, create a tuple and insert it, don’t create a map wrapped into a single-element tuple. To get the old record by key, one usually uses :ets.lookup/2
, or more sophisticated :ets.select
.
iex|7 ▶ [{_, make, _}|_] = :ets.lookup(:fast_cars_cache, "Red")
iex|8 ▶ new_record = {"Black", make, 1641.469}
iex|9 ▶ :ets.insert(:fast_cars_cache, new_record)
I have linked enough documentation to leave using :ets.select_replace/2
as homework.
Upvotes: 1