I have a rather simple ActiveRecords associations like such (specifically in Rails 4):
An organization has many users
A user belongs to an organization
But in terms of ActiveReocord queries, what's an optimal way to construct a query to return an array of Organizations each with its own array of user ids associated with itself? Basically, I'd like to return the following data structure:
#<ActiveRecord::Relation [#<Organization id: 1, name: "org name",.... user_ids: [1,2,3]>, <Organization id: 2...>]>
... or to distill it even further in JSON:
[{id: 1, name: 'org name', ... user_ids: [1,2,3]}, {...}]
where users is not part of the Organizations table but simply an attribute constructed on the fly by ActiveRecord.
Thanks in advance.
EDIT: After trying a few things out, I came up with something that returned the result in the format I was looking for. But I'm still not sure (nor convinced) if this is the most optimal query:
Organization.joins(:users).select("organizations.*, '[#{User.joins(:organization).pluck(:id).join(',')}]' as user_ids").group('organizations.id')
Alternatively, the JBuilder/Rabl approach #Kien Thanh suggested seem very reasonable and approachable. Is that considered current best practice nowadays for Rails-based API development (the app has the back-end and front-end pieces completely de-coupled)?
The only thing to be aware of with a library solution such as JBuilder or Rabl is to watch the performance when they build the json.
As for your query use includes instead of joins to pull the data back.
orgs = Organization.includes(:users)
You should not have to group your results this way (unless the group was for some aggregate value).
ActiveRecord::Relation gives you some automatic helper methods, one of which is association_ids.
So if you create your own JSON from a hash you can do
orgs.map! {|o| o.attributes.merge(user_ids: o.user_ids).to_json }
EDIT: Forgot to add the reference for has_many http://guides.rubyonrails.org/association_basics.html#has-many-association-reference
Related
The rest api that I have to use provides data over multiple endpoints. The objects in the results might have relations that are are not resolved directly by the api, it rather provides ids that point to the actual resource.
Example:
For simplicity's sake let's say a Person can own multiple Books.
Now the api/person/{i} endpoint returns this:
{ id: 1, name: "Phil", books: [1, 5, 17, 31] }
The api/book/{i} endpoint returns this (note that author might be a relation again):
{ id: 5, title: "SPRINT", author: 123 }
Is there any way I can teach the apollo client to resolve those endpoints in a way that I can write the following (or a similar) query:
query fetchBooksOfUser($id: ID) {
person (id: $id) {
name,
books {
title
}
}
}
I didn't try it (yet) in one query but sould be possible.
Read docs strating from this
At the beggining I would try with sth like:
query fetchBooksOfUser($id: ID) {
person (id: $id) #rest(type: "Person", path: "api/person/{args.id}") {
name,
books #rest(type: "Book", path: "api/book/{data.person.books.id}") {
id,
title
}
}
}
... but it probably won't work - probably it's not smart enough to work with arrays.
UPDATE: See note for similiar example but using one, common parent-resolved param. In your case we have partially resolved books as arrays of objects with id. I don't know how to use these ids to resolve missing fields () on the same 'tree' level.
Other possibility - make related subrequests/subqueries (someway) in Person type patcher. Should be possible.
Is this really needed to be one query? You can provide ids to child containers, each of them runing own query when needed.
UPDATE: Apollo will take care on batching (Not for REST, not for all graphql servers - read docs).
'it's handy' to construct one query but apollo will cache it normalizing response by types - data will be stored separately. Using one query keeps you within overfetching camp or template thinking (collect all possible data before one step rendering).
Ract thinking keeps your data and view decomposed, used when needed, more specialised etc.
<Person/> container will query for data needed to render itself and list of child-needed ids. Each <Book/> will query for own data using passed id.
As an alternative, you could set up your own GraphQL back-end as an intermediary between your front-end and the REST API you're planning to use.
It's fairly easy to implement REST APIs as data sources in GraphQL using Apollo Server and a package such as apollo-datasource-rest which is maintained by the authors behind Apollo Server.
It would also allow you to scale if you ever have to use other data sources (DBs, 3rd party APIs, etc.) and would give you full control about exactly what data your queries return.
I'm looking for the best Ruby way to accomplish this.
I have a Person that has_many Feeds through Subscriptions. So we can do things like Person.feeds, and it gets all the feeds a person is subscribed to.
Problem is, subscriptions are either authorized or deauthorized. What is the best way to make Person.feeds respect the Authorized status bit on the Subscription model?
So we can do something like Person.feeds.where(:status => authorized).
You can call this with a command like the following:
#person.feeds.joins(:subscription).where(subscriptions: { status: 'authorized' })
N.B. the joins takes the association's format, singular in this case, while where takes the table name, typically pluralised.
What this does in order is:
Loads the feeds belonging to a person
Joins these feeds to their subscription
Queries the subscriptions table to return only the feeds where the subscription is active
To refactor this, I'd include a couple of methods in the relevant models:
# feed.rb
scope :active, -> { joins(:subscription).where(subscriptions: { status: 'authorized' }) }
# person.rb
def active_feeds
feeds.active
end
Then, you can just call #person.active_feeds to get the results you want from anywhere in your code base.
(There's also the added bonus of Feed.active being available anywhere should you wish to display active feeds outside of a user's scope.)
Hope that helps - let me know if you've any questions.
I'm still a bit lost when it comes to Sorted Sets and how to best construct them. Currently I have a simple set of activity on my site. Normally it will display things like User Followed, User liked, User Post etc. The JSON looks something like...
id: 2808697,
activity_type: "created_follower",
description: "Bob followed this profile",
body: null,
user: "Bob",
user_id: 99384,
user_profile_id: 233007,
user_channel_id: 2165811,
user_cube_url: "bob-anerson",
user_action: "followed this profile",
buddy: "http://s3.amazonaws.com/stuff/ju-logo.jpg",
affected: "Bill Anerson is following Jon Denver.",
created_at: "2014-06-24T20:34:11-05:00",
created_ms: 1403660051902,
profile_id: 232811,
channel_id: 2165604,
cube_url: "jondenver",
type: "profiles",
So if the activity type can be multiple things (IE Created Follow, Liked Event, Posted News, ETC) how would I go about putting this all in a sorted set? I'm already sure I want the score to be the created_ms but the question is, can I do multiple values in a sorted set that all have keys as fields? Should most of this be in a hash? I realize this is a fairly open question but after trying to wrap my head around all the tutorials Im just concerned about setting up the data structure before had so I dont get caught to deep in the weeds.
A sorted set is useful if you want to... keep stuff sorted! ;)
So, I assume you're interested in keeping the activities sorted by their creation time (ms). As for storing the actual data, you have two options:
Use the sorted set itself to store the data, even in native JSON format. Note that with this approach you'll only be able to fetch the entire JSON and you'll have to parse it at the client.
Alternatively, use the sorted to store "pointers" to hashes - i.e. the values will be key names in which you'll store the data. From your description, this appears the preferable approach.
I have a code section like the following:
users = User.all(:fname => "Paul")
This of course results in getting all users called "Paul". Now I only need some of the columns available for each user which leads to replacing the above line by something like this:
users = User.all(:name => "Paul", :fields => [:id, :fname, :lname, :email])
Until now everything works as expected. Unfortunately now I want to work with users but as soon as I use something like users.to_json, also the other columns available will be lazy-loaded even due the fact, that I don't need those. What's the correct or at least a good way to end up with users only containing the attributes for each user that I need?
An intermediate object like suggested in How to stop DataMapper from double query when limiting columns/fields? is not a very good option as I have a lot of places where would need to define at least twice which fields I need and also I would loose the speed improvement gained by loading only the needed data from the DB. In addition such an intermediate object also seems to be quite ugly to build when having multiple rows of the DB selected (=> multiple objects in a collection) instead of just one.
If you usually works with the collection using json I suggest overriding the as_json method in your model:
def as_json(options = nil)
# this example ignores the user's options
super({:only => [:fname]}.merge(options || {}))
end
You are able to find more detailed explanation here http://robots.thoughtbot.com/better-serialization-less-as-json
Is it possible to simply get the people you are following with just an id and full name? I do not need any of the additional data, it's a waste of bandwidth.
Currently the only solution I have is:
twitter_client = Twitter::Client.new
friend_ids = twitter_client.friend_ids['ids']
friends = twitter_client.users(friend_ids).map { |f| {:twitter_id => f.id, :name => f.name} }
is there anyway to just have users returned be an array of ids and full names? better way of doing it than the way depicted above? preferably a way to not filter on the client side.
The users method uses the users/lookup API call. As you can see on the page, the only param available is include_entities. The only other method which helps you find users has the same limitation. So you cannot download only the needed attributes.
The only other thing I'd like to say is that you could directly use the friends variable, I don't see any benefit of running the map on it.