Why does slice require ! to work? - ruby-on-rails

I am using the slice method to create a new object from the attributes of another object.
What I find odd is that, in MiniTest, printing out
user_group.attributes.slice(ArchivedUserGroup.attribute_names)
returns an empty array. However, using ! ("bang") works and returns all of the user_group attributes.
Can someone give me some insight into why slice works this way?
UserGroup.where(user_id: self.id).each do |user_group|
ArchivedUserGroup.create(
user_group.attributes.slice!(ArchivedUserGroup.attribute_names)
)
end

Hash#slice! returns hash with removed elements while Hash#slice returns selected elements:
{ foo: 1, bar: 2 }.slice(:foo, :bar)
=> {:foo=>1, :bar=>2}
{ foo: 1, bar: 2 }.slice!(:foo, :bar)
=> {}
Also, slice! (with a bang) mutates hash (by removing non-sliced elements), so by conventions, this method has a bang.
It seems like you have not attributes with ArchivedUserGroup.attribute_names keys, so slice! just returns attributes.

Related

Why does Ruby keep the relationship to a hash when initializing with variable?

maybe I am used to the initialState from React, but I tried doing something similar in Ruby and I noticed it updates the hash everywhere I initialized it.
Here's an example:
iniatializing_hash = {likes_count: 0, comments_count: 0}
hash_that_uses_initializer = {first: iniatializing_hash, second: iniatializing_hash}
# update first
hash_that_uses_initializer[:first][:likes_count] += 1
hash_that_uses_initializer[:first][:comments_count] += 1
# expect both first and second were updated
puts "first: #{hash_that_uses_initializer[:first].inspect} and second: #{hash_that_uses_initializer[:second].inspect}"
# returns first: {:likes_count=>1, :comments_count=>1} and second: {:likes_count=>1, :comments_count=>1}
I'm guessing this happens because it keeps the connection to the place in memory the hash is loaded.
Is there another elegant way to use an 'initialState' hash in Ruby?
Thanks!
Hashes are passed by reference. When you assign iniatializing_hash, it does not assign a copy of the hash - it assigns a pointer. When you change the values of the hash that you assigned to :first, you're also changing the values of the hash at :second because they're both the same hash.
You can prove they're the same location in memory by calling object_id method on the object:
echo('same hash') if iniatializing_hash.object_id == hash_that_uses_initializer[:first].object_id == hash_that_uses_initializer[:second].object_id
If you want a copy, then you need a deep copy.
def deep_copy(initial_hash)
Marshal.load(Marshal.dump(initial_hash))
end
hash_that_uses_initializer = {first: deep_copy(iniatializing_hash), second: deep_copy(iniatializing_hash)}
Now the hash stored at :first and the :second will be completely different from each other and different from iniatializing_hash

Retrieve a value from a hash in a hash in an array in a hash

I am trying to get the value of zap in a hash that looks like:
hash = {
:foo => 1,
:bar => [{
:baz => 2,
:zot => {
:zap => 3
}
}]
}
hash.dig breaks as soon as it gets to the array.
If it's important, this is a step in an if/elsif/else statement checking for different error messages. (i.e. elsif zap == 3)
I would do something like this:
hash[:bar].first.dig(:zot, :zap)
I believe you are incorrect, and dig in fact works on any object with a dig method. Dig is defined both for arrays and hashes. Also, if I define a dig method on a custom object:
o = Object.new
def o.dig(*args)
puts args.inspect
return :result
end
then when called like so:
{custom_object: o}.dig(:custom_object,1,2,3)
#-> output: [1,2,3]
#=> :result
you can see that dig gets called on o with the remaining arguments ([1,2,3]) and returns whatever the custom dig method returns.
What you may have missed is that for arrays, you need to use a numeric index, or dig raises a type error when it gets called on the array. So hash.dig(:bar, 0, :zot, :zap) is what you probably want. (credit to Alex for beating me to the punch).

When to use slice vs. permit in ActionController::Parameters?

Assuming I have an ActionController::Parameters object like
params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
I can call slice on it or permit to get/allow only certain parameters.
At first sight, they return the same thing
> params.slice(:a)
=> {"a"=>1}
> params.permit(:a)
[18:21:45.302147] Unpermitted parameters: b, c
=> {"a"=>1}
But if I call to_h on it params.slice(:a).to_h returns an empty hash, while params.permit(:a).to_h returns an hash with key :a. As far as I understood, this is the case, because :a was not permitted.
What I wonder now is, what is the use case of slice, if I could just use permit?
One difference I could think of is permit cuts nested hash if you don't explicitly specify the nested keys while slice allows nested hash:
# params = { a: 'a', nested: { nested_1: 1, nested_2: 2 } }
params.permit(:a, :nested) # => { a: 'a' }
params.slice(:a, :nested) # => { a: 'a', { nested_1: 1, nested_2: 2 } }
Another difference is in Rails 4, permit won't raise ActiveModel::ForbiddenAttributes when calling in .update_attributes(...) (answered here):
user.update_attributes(params.slice(:email)) # will raise ActiveModel::ForbiddenAttributes
user.update_attributes(params.permit(:email)) # wont raise error
slice gives ability to slice a hash with selected keys.
where as .permit returns a new ActionController::Parameters instance that includes only the given filters and sets the permitted attribute for the object to true. This is useful for limiting which attributes should be allowed for mass updating.
I would say slice is for everything dealing with Hash and permit is created using slice pattern but more in context of url params.
Hope it helps!
Also read this: http://apidock.com/rails/ActionController/Parameters/permit

Pluck doesn't return a bother records they have the same name

I have a pluck that is turned into a hash and stored in a variable
#keys_values_hash = Hash[CategoryItemValue.where(category_item_id: #category_item.id).pluck(:key, :value)]
If 2 records have the same :key name then only the most recent record is used and they aren't both added to the hash. But if they have the same value and different keys both are added to the hash.
This also occurs if I swap :key and :value around (.pluck(:value, :key)). If they have now the same value it only uses the most recent one and stores that in the hash. But having the same key is now fine.
I'm not sure of this is being caused by pluck or from being sorted in a hash. I'm leaning towards pluck being the culprit.
What is causing this and how can I stop it from happening. I don't want data being skipped if it has the same key name as another record.
I'm not sure why you need convert pluck result into a Hash, because it was an Array original.
Like you have three CategoryItemValue like below:
id, key, value
1, foo, bar1
2, foo, bar2
3, baz, bar3
when you pluck them directly, you will get a array like:
[ ['foo', 'bar1'], ['foo', 'bar2'], ['baz', 'bar3'] ]
but when you convert it into a hash, you will get:
{'foo' => 'bar2', 'baz' => 'bar3' }
because new hash value will override the old one if key ( foo in the example above) exists.
Or you could try Enumerable#group_by method:
CategoryItemValue.where(...).pluck(:key, :value).group_by { |arr| arr[0] }

Using best_in_place gem how do I specify a nil value for a select tag?

The answer on this question has provided me with a nice roadmap for how to generate select tags with data from a collection on an association.
This works nicely and everything is going great.
The issue I have now is, how do I handle an empty collection?
With the regular :type => :input, I can just specify :nil => "Some nil message here".
But that doesn't seem to work for the collection, and to make matters worse, when there is nothing in the collection it seems to be displaying some integers (i.e. 1 and 2). I am assuming those are the IDs from the previously displayed objects in the collection, but for obvious reasons that doesn't make much sense.
Any ideas on how I can handle an empty collection with this gem?
Thanks.
Edit 1:
One alternative is to just put my original best_in_place helper tag inside an if statement for when a collection is not nil. But then how does the user edit it when it is blank? Perhaps there may be no way to handle this, because it would involve creating a new record in the collection.
I use a "workaround" for the empty options in a select tag, it could help you:
:type => :select, :collection => #my_colletion || [[I18n.t('common.none'), -1]]
When #my_colletion is nil, it shows a choice named 'None' with id = -1 (wich is not that bad to handle in the backend).
This part of code assumes the #my_collection is an array of arrays like [ [key, value], [key, value], ... ] OR nil.
Although, if you want your MyModel.all collection to fit the conditions for best_in_place, you can use the following:
#my_collection = MyModel.all.map{ |object| [object.name, object.value] }
# => this returns an array like [ [key, value], [key, value], ... ]
# => returns an empty array (NOT nil) if there is no entry in the DB
About the -1 id:
Using the -1 id as 'none' is easy because you don't need to explicitly handle the value nil (tests, etc). With the -1 id, you can use the following:
MyModel.where(id: params[:id]).first # => Returns the first object that has the params[:id]
# if params[:id] is -1, it will return nil and not raise an error.
I hope it helped :)

Resources