Is there a function that allows me to add a hash of data to an initialized ApplicationRecord such that the following would work?
$ foo = Foo.new(bar: 4)
class Foo < ApplicationRecord {
:id => nil,
:bar => 4,
:x => nil,
:y => nil
}
$ data = {x: 5, y: 6}
$ foo.INSERT_METHOD_HERE(data)
class Foo < ApplicationRecord {
:id => nil,
:bar => 4,
:x => 5,
:y => 6
}
I thought such a method exists but I cannot recall the name
I have a query* that results in the following:
#<ActiveRecord::Relation [
#<BookRank id: 2, book_id: 2, list_edition_id: 1, rank_world: 5, rank_europe: 1>,
#<BookRank id: 3, book_id: 1, list_edition_id: 1, rank_world: 6, rank_europe: 2>,
#<BookRank id: 8, book_id: 2, list_edition_id: 3, rank_world: 1, rank_europe: 1>,
#<BookRank id: 9, book_id: 1, list_edition_id: 3, rank_world: 2, rank_europe: 2
]>
What I am trying to get is a hash like this:
{
book_id => {
list_edition_id => {
"rank_world" => value,
"rank_europe" => value
}
}
}
(The cherry on top would be to order the hash by the rank_world value for the lowest list_edition_id, but that may be too complex perhaps.)
ranks_relation.group_by(&:book_id) gives me a hash where the book_ids are keys, but then the ranks data is still in arrays:
{
2 => [
#<BookRank id: 2, book_id: 2, list_edition_id: 1, rank_world: 5, rank_europe: 1>,
#<BookRank id: 8, book_id: 2, list_edition_id: 3, rank_world: 1, rank_europe: 1>
],
1 => [
#<BookRank id: 3, book_id: 1, list_edition_id: 1, rank_world: 6, rank_europe: 2>
#<BookRank id: 9, book_id: 1, list_edition_id: 3, rank_world: 2, rank_europe: 2>
]
}
How should I proceed?
*EDIT: This is the model structure and query. Another user asked for it:
class Book < ActiveRecord::Base
has_many :book_ranks, dependent: :destroy
end
class List < ActiveRecord::Base
has_many :list_editions, dependent: :destroy
end
class ListEdition < ActiveRecord::Base
belongs_to :list
has_many :book_ranks, dependent: :destroy
end
class BookRank < ActiveRecord::Base
belongs_to :book
belongs_to :list_edition
has_one :list, through: :list_edition
end
For the query, I already use two arrays with the relevant IDs for Book and ListEdition:
BookRank.where(:book_id => book_ids, :list_edition_id => list_edition_ids)
Try this
record = your_record
hash = {}
record.each do |record|
hash[record.book_id] ||= {}
hash[record.book_id][record.list_edition_id] = {
'rank_world' => record.rank_world,
'rank_europe' => record.rank_europe
}
end
# hash will then be {2=>{1=>{"rank_world"=>5, "rank_europe"=>1}, 3=>{"rank_world"=>1, "rank_europe"=>1}}, 1=>{1=>{"rank_world"=>6, "rank_europe"=>2}, 3=>{"rank_world"=>2, "rank_europe"=>2}}}
This will iterate through record only once.
Hey #gibihmruby (nice name btw),
so since you asked in the comments for a more specific description of my ugly approach using just group_by and friends, here is my proposal:
rel.group_by(&:book_id).map do |k, v|
[k, v.group_by(&:list_edition_id)]
end.to_h
would yield a structure like
{2=>
{1=>
[#<struct BookRank
id=2,
book_id=2,
list_edition_id=1,
rank_world=5,
rank_europe=1>],
3=>
[#<struct BookRank
id=8,
book_id=2,
list_edition_id=3,
rank_world=1,
rank_europe=1>]},
1=>
{1=>
[#<struct BookRank
id=3,
book_id=1,
list_edition_id=1,
rank_world=6,
rank_europe=2>],
3=>
[#<struct BookRank
id=9,
book_id=1,
list_edition_id=3,
rank_world=2,
rank_europe=2>]}}
You then would have to map the most inner object to the attributes you want. If you are sure that the combination of book_id and list_edition_id is unique, you can get rid of the array wrapping and then map to the required attributes. You can use slice for ActiveRecord objects. The mapping would then be
rel.group_by(&:book_id).map do |book_id, grouped_by_book_id|
[
book_id,
grouped_by_book_id.group_by(&:list_edition_id).map do |list_ed_id, grouped|
[list_ed_id, grouped.first.slice(:rank_world, :rank_europe)]
end.to_h
]
end.to_h
Since I didn't create a model but simply used structs (as you can see in my example above), I didn't really test the last bit by myself. But it should work like this, please comment if you found a mistake or have more questions. I still hope someone comes up with a better solution since I was looking for one myself way too often now.
Cheers :)
edit: minor corrections
ranks_relation.
group_by(&:book_id).
map do |id, books|
[id, books.map do |book|
[
book.list_edition_id,
{
"rank_world" => book.rank_world,
"rank_europe" => book.rank_europe
}
]
end.sort_by { |_, hash| hash["rank_world"] }.to_h
]
end.to_h
I have a model enum like:
class User < AR::Base
enum status [:pending, :member, :banned]
end
Now I want to output the string value of 'banned' but it outputs the int value:
User.statuses[:banned]
I'm not sure that's how they work. Looking at some docs:
http://api.rubyonrails.org/classes/ActiveRecord/Enum.html
You would have something like
# User.status = 2
User.status = "banned"
This is something of a non-answer, but the question belies the implementation of ActiveRecord::Enum:
# to get the string value for User.statuses[:banned]…
"banned"
# or
:banned.to_s
# to get the string value for all values in the User.statuses enum…
User.statuses.keys
# => ["pending", "member", "banned"]
The key isn't the important part here, really. All Rails is doing is taking the array of symbols you give it here…
enum status: [:pending, :member, :banned]
…and assigning it to a hash with incremented integer values while providing you a bunch of convenient methods for accessing the value:
user.status #=> 'pending'
user.pending? #=> true
You can verify this if you like…
User.defined_enums.class #=> Hash
User.defined_enums
#=> { "status" => { "pending" => 0, "member" => 1, "banned" => 2 } }
Is there a simple way in Ruby 2/Rails 3 to transform this:
{a: {b: {"1" => 1, "2" => 2}, d: "Something"}, b: {c: 1}}
into this:
{"a.b.1" => 1, "a.b.2" => 2, "a.d" => "Something", "b.c" => 1}
I'm not talking about this precise hash but transform any hash into a dot-notation hash.
Here's the cleanest solution I could come up with right now:
def dot_it(object, prefix = nil)
if object.is_a? Hash
object.map do |key, value|
if prefix
dot_it value, "#{prefix}.#{key}"
else
dot_it value, "#{key}"
end
end.reduce(&:merge)
else
{prefix => object}
end
end
Test:
input = {a: {b: {"1" => 1, "2" => 2}, d: "Something"}, b: {c: 1}}
p dot_it input
Output:
{"a.b.1"=>1, "a.b.2"=>2, "a.d"=>"Something", "b.c"=>1}
In this post, slice function is used to get only necessary elements of params. What would be the function I should use to exclude an element of params (such as user_id)?
Article.new(params[:article].slice(:title, :body))
Thank you.
Use except:
a = {"foo" => 0, "bar" => 42, "baz" => 1024 }
a.except("foo")
# returns => {"bar" => 42, "baz" => 1024}
Inspired in the sourcecode of except in Rails' ActiveSupport
You can do the same without requiring active_support/core_ext/hash/except
# h.slice( * h.keys - [k1, k2...] )
# Example:
h = { a: 1, b: 2, c: 3, d: 4 }
h.slice( * h.keys - [:b, :c] ) # => { a: 1, d: 4}
Try this
params = { :title => "title", :other => "other", :body => "body" }
params.select {|k,v| [:title, :body].include? k } #=> {:title => "title", :body => "body"}
Considering only standard Ruby.
For Ruby versions below 3, no.
For Ruby 3, yes. You can use except.