How in rails, do you call update_attributes() on a active record object, and have it create appropriate rows in one-to-many associated table based on an array param.
Is this possible? Or do I need to manually loop through that param's array and insert the many rows manually via create_record() etc?
To be clear, I might have users table with 1-to-many addresses. I want to call users.update_attributes() passing in the user details to be updated, but also provide an array of addresses mapping to the addresses table.
In your User model:
model User
has_many :addresses
accepts_nested_attributes_for :addresses
end
now user can be created with this set of params
params = { user: {
name: 'Dimitri', address_attributes: [
{ country: 'Georgia', city: 'Abasha', line: '35 Kacharava Str.' },
{ country: 'USA', city: 'Los Angeles', line: '10 Infinite Loop' },
{ country: '', _destroy: '1' } # this will be ignored
]
}}
User.create(params[:user])
more details can be found from here.
Related
I am creating a Rails api for teachers to rank students based on certain criteria. I have four models: classroom, student, criterion and rank.
Students/Criteria are many to many through Rank
Students/Classroom are many to many
Rank is a join table between Student/Criteria with the additional field of rank, which is an integer between 1-4.
I am able to return the list of Students belonging to a Classroom in a response (1 relation deep) by allowing Classroom.students through in my classroom serializer. How can I return each student's ranks nested within students in my Classroom response (2 relations deep) from my API? Ideal response as below:
Classroom_A:
{
id: "123",
name: "classroom A",
students: [
{ id: "456"
name: Juanita,
gender: female,
ranks: [
{ id: "789",
student_id: "456",
name: "willingness to help others",
rank: "4"
},
{ id: "101",
student_id: "456",
name: "Leadership",
rank: "3"
} ...
]
},
{ id: "232"
name: Billy,
gender: male,
ranks: [
{ id: "789",
student_id: "232",
name: "willingness to help others",
rank: "3"
},
{ id: "101",
student_id: "232",
name: "Leadership",
rank: "3"
} ...
]
}
]
}
Thanks in advance.
A similar question was posted at Rails: Serializing deeply nested associations with active_model_serializers (thanks for the link #lam Phan)
However, the most upvoted answer for that post was not super clear and there was no code example. Super dissapointing. I also looked into the JsonApi adapter and was not able to get the solution I was looking for. In the end I ended up taking the same route as the OP for the linked question: in the serializer I wrote my own function and manually sideloaded the other data that I wanted. I was hoping to do this the "Rails way" but in the end I chose to just get it done.
class ClassroomSerializer < ApplicationSerializer
attributes :id, :name, :school_name, :students
def students
customized_students = []
object.students.each do |student|
custom_student = student.attributes
custom_student['ranks'] = student.student_ranks
customized_students.push(custom_student)
end
customized_students
end
end
To throw some context around our models: an organization has users (with different roles), and users have blog posts.
I have a use case where I need to populate two tables in our UI with post usage data by users in an organization (i.e. posts as in blog posts). One table shows a summary of the total post count for the entire organization grouped by the role of the user (e.g. admins, editors, and writers). The other table, though, would show all users and the post count for each one of them, regardless of their role.
However, this API endpoint/s will also be public and used by organization admins to determine post usage, so we want it to be as intuitive as possible.
We already have an endpoint that returns data about users (email, name, etc.)
The format is akin to:
{
current_page: 1,
per_page: 50,
next_page: 2,
previous_page: null,
total_pages: 3,
total_count: 150,
users: [
{
id: 1,
name: "John Doe",
email: "email#example.com",
...
},
...
]
}
The aggregated data we need is just the post count for an organization grouped by user roles, as well as the post count per user (for all users). That is, the number of posts that a user has published over a time period (which would be provided in the URL params).
We've bounced this idea for a second, and have come up with some alternatives, the one making more sense so far being serving data through one endpoint that allows for aggregating data based on defined aggregation patterns.
For example, we could do the following to get the information for the first table, which needs aggregated data for an entire organization:
# /usage/posts?from=&to=&aggregation=user_role
{
usage: {
admin: {
post_count: admin_posts.count,
published_post_count: admin_posts.published.count
},
editor: {
post_count: editor_posts.count,
published_post_count: editor_posts.published.count,
},
writer: {
...
}
}
}
Then for the second table, which needs to include all users in an organization and their post counts, we can do something along the lines of:
# /usage/posts?from=&to=&aggregation=user(default)
{
current_page: 1,
per_page: 50,
next_page: 2,
previous_page: null,
total_pages: 3,
total_count: 150,
usage: [
{
id: user.id,
post_count: user.posts.count,
email: user.email,
name: user.name,
role: user.role,
...
},
{
id: user.id,
post_count: user.posts.count,
email: user.email,
name: user.name,
role: user.role,
...
},
...
]
}
Does this payload structure seem intuitive? Not just for our own usage and to be able to populate our UI, but also for our users to fetch data from that they can use however they see fit.
I'd appreciate any feedback and thoughts!
I have
#total = Purchase::Total.find(1);
Total model have:
has_many :items
belongs_to :member
belongs_to :company
..................
Also companies model has
has_many :addresses
has_one :subscription
..................
and a lot more
How can I get a tree from the #total object containing all the has_one, belongs_to dependencies?
I.E.
<Purchase::Total id: 3, member_id: 4, created_at: \"2015-11-25 14:47:46\", updated_at: \"2015-11-25 14:47:46\", affiliate_company_id: nil, is_paid: false, currency: 1, company_id: 37020, ser_id: 2>
<Company id: 37020, name: \"Andrew\", parent_id: 37019, member_company_id: 37019, payment_company_id: 37019, widget_id: 3003359>
And so ..... (I did the example with: #total.inspect and #total.company.inspect), and I need something like inspect to return automatically all the objects.
Using reflect_on_all_associations
Take a Queue and a Hash and add Total (model name) to it.
Pop a model name, get all associated models and add them queue. Also, using the tablize name of current model, create a new entry in hash and add the tablized names of associated models.
If queue is not empty, go to 2.
At the end, your hash should look like:
{ total: { company: [ :subscription, :addresses ] }, items: { associated: { another: :another_one } } }
Then you can use this in your query:
Total.where().join(hash[:total])
It will fetch all the associated data as well. Then you can simply loop through the attributes. If attribute type is ActiveRecord (or similar), then its an associated model data.
Originally, I specified a relationship where contact has_many services. Therefore, services has a foreign key of contact_id:
class Contact
include Mongoid::Document
field :name, type: String
end
class Service
field :name, type: String
field :contact_id, type: Integer
end
Now there is a possibility to add an additional contact to a service, so service has many contacts. However, the contacts that are added are ones that already exist independently. So I do not want to embed one entity inside another. A contact and service will always live independently. No embedding.
So should I just store the ids of the contacts inside an array of Service? In other words, my new models will look like this:
class Contact
include Mongoid::Document
field :name, type: String
end
class Service
field :name, type: String
field :contact_id, type: Integer
field :contact_ids, type: Array, default: []
end
Or is there a better solution to address the many to many problem here (without embedding one document in another)?
For the Many-To-Many, you don't have 36 options : you actually have 2 :
Array of IDs on One side like you did
Array of IDs on Both sides.
The cool thing with the "both sides" solution is that you can find query documents from both collections to get the links.
Example with books and authors :
db.books.findOne()
{
_id: 1,
title: "The Great Gatsby",
authors: [1, 5]
}
db.authors.findOne()
{
_id: 1,
firstName: "F. Scott",
lastName: "Fitzgerald",
books: [1, 3, 20]
}
This model:
class SimCustomer < Customer
index({ user_id: 1 }, { background: true })
belongs_to :user, :inverse_of => :sim_customers
end
inherits from this model:
class Customer
include Mongoid::Document
include Mongoid::Timestamps
field :mail_address, type: String
end
I create the indexes from my terminal:
bundle exec rake db:mongoid:create_indexes
But this creates indexes on the Customer instead of the SimCustomer:
I, [2014-11-13T16:21:17.210343 #11407] INFO -- : MONGOID: Created indexes on Customer:
I, [2014-11-13T16:21:17.210381 #11407] INFO -- : MONGOID: Index: {:user_id=>1}, Options: {:background=>true}
And when I try to batch insert SimCustomer objects it creates Customer objects instead:
SimCustomer.collection.insert(Array.new << {mail_address: "hello#hello.com", user_id: "54652f5b43687229b4060000"})
# => #<Customer _id: 54654b7b6220ff4f28364ee9, created_at: nil, updated_at: nil, mail_address: "hello#hello.com", _type: "Customer">
How can I fix this?
This sets up Single Collection Inheritance:
class SimCustomer < Customer
That means that both Customer and SimCustomer will be stored in the customers collection inside MongoDB and they'll be differentiated using the _type field.
Specifying an index in SimCustomer:
class SimCustomer < Customer
index({ user_id: 1 }, { background: true })
will create the index on the customers collection because that's where SimCustomers are stored.
The same collection chicanery is causing your problem with your bulk insert. If you look at SimCustomer.collection.name you'll find that it says 'customers' so of course SimCustomer.collection.insert will create new Customers. If you want to create SimCustomers by hand then specify the _type:
SimCustomer.collection.insert(
_type: 'SimCustomer',
mail_address: "hello#hello.com",
user_id: "54652f5b43687229b4060000"
)
Note that I dropped that strange looking Array.new << stuff, I don't know where you learned that from but it is unnecessary when inserting on object and odd looking if you were inserting several, if you want to insert several then just use an array literal:
SimCustomer.collection.insert([
{ ... },
{ ... },
...
])
Your next problem is going to be that string in user_id. That really should be a Moped::BSON::ObjectId or you'll end up with a string inside the database and that will make a mess of your queries. Mongoid may know what type a property should be but neither Moped nor MongoDB will. You'll want to use Moped::BSON::ObjectId('54652f5b43687229b4060000') instead.