Get other field if this is included in the array object - ruby-on-rails

Title may sound off but below explains more.
I need to know if an item is included in an array object:
[
{ id: 12345, name: "Bob", email: "bob#builder.com" },
...
{ id: 13456, name: "job", email: "joe#farm.com" }
]
In english: If this email present, give me their id
users = User.all
users.any?{|u| u.email == "bob#builder.com"} # true
That will be true. Now, how to get the id of the user which is "12345"? Note, I will not know the id.

Since you seem to be using ActiveRecord, it would be faster to query the database for the email:
# returns the user id or nil, if not found
User.find_by(email: 'bob#builder.com').pluck(:id)

You can try like this if you have array of hashes:
arr.map do |h|
h[:id] if h[:email] == "bob#builder.com"
end.compact
It will return the value of id in an array of all the hashes which satisfies the condition.

Related

What is the best way to loop through a collection of records and pass back an object to the front end?

I have a controller that returns user reports, and one of the methods sums up the points of said reports, per user. I want to pass back an object of this data to the front end so it can be displayed. Ideally my object would be shaped like this:
data: {
users: {
$user_id: {
name: "Foo Bar",
points: 100
},
$user_id: {
name: "Foo Bar Two",
points: 10
}
}
}
However my current implementation is not building the object like this, and simply adding to one big object.
My code looks like this:
def user_points
hash = {}
User.all.each do |u|
user_points = Report.select("points").where("user_id = ?", u.id).sum("points")
hash.merge!(
user:
{
first_name: u.first_name,
last_name:u.last_name,
time_zone: u.time_zone
}
)
end
render json: { data: hash }
end
and the resulting object only included the last user in one big object
data:
user:
first_name: "Test"
last_name: "Test"
points: 200
time_zone: "Pacific Time (US & Canada)"
You can also achieve the same result by joining both the table and then performing aggregation on joined table.
select users.id, users.name, sum(reports.points) as points from users join reports on users.id = reports.user_id group by users.id;
sql-fiddle
Thank you max for the comment.
def user_points
result = User.join(:reports)
.select(
:first_name,
:last_name,
Report.arel_table[:points].sum.as(:points),
:time_zone
)
.group(:id)
render json: { data: result }
end
Output:
data:
first_name: "Test1"
last_name: "Test1"
points: 100
first_name: "Test2"
last_name: "Test2"
points: 200
first_name: "Test3"
last_name: "Test3"
points: 300
As mentioned by dbugger you need to provide a unique key for each hash entry otherwise merge will just replace an existing value.
For example:
{a: :foo}.merge(b: :bar)
=> {:a=>:foo, :b=>:bar}
and
{a: :foo}.merge(b: :bar).merge(a: :foo_bar)
{:a=>:foo_bar, :b=>:bar}
You might want to consider returning a json array rather than an object with unique property names.
maybe something like this?
def user_points
result = User.all.map do |u|
points = Report.select("points").where("user_id = ?", u.id).sum("points")
{
first_name: u.first_name,
last_name:u.last_name,
time_zone: u.time_zone
points: points
}
end
render json: { data: result }
end

How could we replace the ? from the rails sql query

scope :accessible_by, -> (current_user) { where("loc_primary_email= ? OR loc_backup_email= ? ",current_user.email, current_user.email) }
How could I replace ? with :email so, that I don't need to pass current_user.email twice in query
You can replace it just like that, for the "?":
scope :accessible_by, -> (current_user) { where("loc_primary_email = :email OR loc_backup_email = :email ", email: current_user.email) }
But then the argument for where must contain a hash, where the email key must be present.
You can check the docs for this, using an array of as argument (of course, you can omit the brackets);
Alternatively, you can use named placeholders in the template, and
pass a hash as the second element of the array. The names in the
template are replaced with the corresponding values from the hash.
User.where(["name = :name and email = :email", { name: "Joe", email: "joe#example.com" }])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe#example.com';

Using group_by but return an array of hashes

I simply want to group cities by their state and from there have the array of the hash key (i.e. State Name) return an array of hash data pertaining to it's cities. Right now I have something like this:
City.all.group_by { |c| c.state.name }
Which will return:
{
"Illinois": [# < City id: 3, name: "Chicago", state_id: 3 > ],
"Texas": [# < City id: 2, name: "Houston", state_id: 2 > ],
"California": [# < City id: 1, name: "Los Angeles", state_id: 1 > ],
"New York": [# < City id: 4, name: "New York City", state_id: 4 > ]
}
Notice how it returns an array of rails objects. Instead I want to return an array of hashes with certain attributes, like their id and name.
The reason the grouped values are Rails objects (your models) is due to the fact that you also start with these objects. You can use the attributes method to retrieve the attributes of a model instance as a hash.
The following achieves the result you want:
City.all.group_by { |city| city.state.name }
.transform_values { |cities| cities.map(&:attributes) }
If you only want specific attributes, use slice instead:
City.all.group_by { |city| city.state.name }
.transform_values { |cities| cities.map { |city| city.slice(:id, :name) } }
Note that slice will return an ActiveSupport::HashWithIndifferentAccess instance. Which mostly can be used in the same manner as a normal hash, but returns the same value for both hash[:name] and hash['name']. If you rather use a normal hash append a to_hash call after the slice call.
This should be enough for you
City.all.group_by { |c| c.state.name }.map {|k,v| [k, v.attributes] }.to_h
and to select only specified attributes do
v.attributes.slice(:name, :id)
One of the easiest approach is to convert it into json object
City.all.as_json.group_by { |c| c.state.name }
this will fix the issue

How to perform inner join on array and table

I have an array of hash, which stores data from api. How can I perform inner join on it with a table.
> posts
[{ uuid: 'post1', user_uuid: 'user1' },
{ uuid: 'post2', user_uuid: 'user2' }]
> User.all
#<ActiveRecord::Relation [#<User id: 1, uuid: "user1", name: "User1">]>
I expect to get an array like
[{ uuid: 'post1', user_uuid: 'user1', user_name: 'User1' }]
user2 does not exist in table, so it will not be in the result array.
this is sample for you code above not inner join since the other table (posts) is not relational table in database
arr1 = Array.new
users = User.all
users.each do |user|
post = posts.detect { |d| d[:user_uuid] == user.uuid }
# detect will check if here is matching between user and posts content
arr1 << {uuid: post[:uuid], user_uuid: post[:user_uuid], user_name: user.name} if post.present?
end
puts arr1

Array of hashes where hash is returned by a function in jbuilder

I have a PORO TutorProfileHandler that has a function json that returns a hash.
class TutorProfileHandler
def initialize(opts)
#profile = opts[:tutor_profile]
end
def json
tutor = #profile.tutor
return {
id: tutor.id,
first_name: tutor.first_name,
last_name: tutor.last_name.first + '.',
school: #profile.school,
avatar: #profile.avatar.url,
bio: #profile.bio,
academic_level: #profile.academic_level,
headline: #profile.headline,
major: #profile.major,
rate: #profile.rate,
rating: #profile.rating,
courses: JSON.parse(#profile.courses),
video_url: #profile.video_url
}
end
end
In my index_tutor_profiles.json.jbuilder, I would like to generate
{
tutor_profile: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_sum: 20
}
However when I do this
json.tutor_profiles (#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
json.tutor_sum #tutor_sum
It gives me an empty array for tutor_profiles.
However if I move everything from TutorProfileHandler.json to the jbuilder file, it works. How do I explicitly include the hash returned by TutorProfileHandler.json in the jbuilder array?
Note: This returns an array, but it creates a new key-value pair array:
json.tutor_profiles json.array(#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
Result:
{
array: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_profile: [],
tutor_sum: 20
}
There is a ugly approach:
json.tutor_profiles #tutor_profiles do |profile|
tmp_json = TutorProfileHandler.new({tutor_profile: profile}).json
json.(tmp_json, *(tmp_json.keys))
end
I think the best practise is directly nesting inside model. You can get more information from the its github page.

Resources