referencing embedded resources mongo mongoid ruby rails - ruby-on-rails

ruby 2.50
rails 5.1.4
gem 'mongoid'
gem 'mongoid_paranoia'
gem 'mongoid-tree'
gem 'mongoid-autoinc'
gem 'mongoid-userstamps'
I am having trouble using an embedded resource.
I have an Account model.
class Account
store_in collection: 'accounts'
has_many :email_routes, dependent: :destroy
embeds_many :labels, as: :labelable
etc..
end
As you can see, each account has many labels.
class Label
embedded_in :labelable, polymorphic: true
belongs_to :email_routes
field :text, type: String
field :color, type: String
end
The available labels are specified at the account level...
and I have an EmailRoute, which I want to apply some labels to..
class EmailRoute
belongs_to :account
field :email_address, type: String
has_many :labels, as: :labelable, dependent: :nullify
# field :labels, type: Array, default: []
end
In the email_routes_controller, I have a simple update action..
def update
selected_labels = params[:labels]
#email_route.update_attributes!(labels: selected_labels)
render json: #email_route
end
params[:labels] contains an array of Label objects. When I patch to the controller, I get the following error/message.
Referencing a(n) Label document from the EmailRoute document via a relational association is not allowed since the Label is embedded.
summary:
In order to properly access a(n) Label from EmailRoute the reference would need to go through the root document of Label. In a simple case this would require Mongoid to store an extra foreign key for the root, in more complex cases where Label is multiple levels deep a key would need to be stored for each parent up the hierarchy.
resolution:
Consider not embedding Label, or do the key storage and access in a custom manner in the application code.):"
Is there any way I can work around this? I know there are many articles regarding embedded but none seemed to quite fit this particular case. Any help is greatly appreciated, as I don't want to lose label data and need them to be specified at the account level.
Thanks!
edit Is something like this going to be the only solution?
https://www.cookieshq.co.uk/posts/switching-relationships-with-mongoid
edit 2 ... I have managed to get to this point when calling #account.email_routes[0].labels from within the rails console. The strings in the nested array within selector are the labels I meant to add. However, when I render :json #account.email_route, the labels array is returned empty. How do I use the information within the selector?
#<Mongoid::Criteria
selector: {"_id"=>{"$in"=>["Critical Test updates", "Chill", "New", >"Make", "Cool", "1", "2", "3"]}}
options: {}
class: Label
embedded: true>

This getter in the EmailRoute model does the trick
def labels
account.labels.where(:_id.in => self[:labels])
end

Related

Include Relationship in Rails application with Grape

I'm returning a resource in API (I'm, using Grape) but I would like to return also the relationship objects (as included objects just as an ember application expects). How can I achieve that ? My serializer is as follow:
class JudgeSerializer < ActiveModel::Serializer
attributes :ID, :FIRST_NAME, :LAST_NAME, :FULL_NAME
end
My model:
class Judge < ApplicationRecord
self.primary_key = 'id'
self.table_name = 'judge'
has_many :judgecourts, class_name: 'Judgecourt', primary_key: 'ID',foreign_key: 'JUDGE_ID'
end
I'm returning this resource this way:
desc 'Return a specific judge'
route_param :id do
get do
judge = Judge.find(params[:id])
present judge
end
end
It would be good to generate something like this:
data
:
{type: "judges", id: "1", attributes: {…}, relationships: {…}}
included
:
Array(1)
0
:
{type: "judgecourts", id: "1", attributes: {…}}
Well it looks like there are two topics in your question:
1. How to include a relationship in a ActiveModelSerializer
This can be done pretty easily by adjusting your active model serializer and adding the relation, so that ActiveModelSerializer knows that it must include the relation in the serialized objects:
class JudgeSerializer < ActiveModel::Serializer
attributes :ID, :FIRST_NAME, :LAST_NAME, :FULL_NAME
has_many :judgecourts
end
This would automatically provide the judgecourts relation inside the serialized json. An example with the classic Post/Comment objects:
2. Use specific format...
The format you specified looks a lot like JSON:API format. If that's what you're really wanting to achieve, then you might be better off using the JSON:API adapter built-in ActiveModelSerializer. For this, you need to tell AMS to use the proper adapter, maybe through an initializer file :
# ./initializers/active_model_serializer.rb
ActiveModelSerializers.config.adapter = :json_api
After this, your json should be formatted the way you seem to expect. I'm no expert in the json api specification, so there might be some more things to tweak. You'll be able to find more information about this adapter in ActiveModelSerializers wiki page on adapters, section JSON API.
Here's what I get with my Post/Comment example:
Note that there are other gems out there that were built to specifically address the JSON API specification, such as jsonapi-rb and Netflix Fast JSON API. They might be of interest for you.

Saving nested model in Rails 4

Kinda new to the Rails thing, in a bit of a spot.
One of the models is dependent on the other in a has_many/belongs_to association.
Basically, when creating a "Post" on my application, a user can also attach "Images". Ideally these are two separate models. When a user chooses a photo, some JavaScript uploads it to Cloudinary and the returned data (ID, width, height, etc) are JSON stringified and set on a hidden field.
# The HTML
= f.hidden_field :images, :multiple => true, :class => "image-data"
# Set our image data on the hidden field to be parsed by the server
$(".image-data").val JSON.stringify(images)
And of course, the relationship exists in my Post model
has_many :images, :dependent => :destroy
accepts_nested_attributes_for :images
and my Image model
belongs_to :post
Where I'm lost is what to do with the serialized image data on the Post controller's create method? Simply parsing the JSON and saving it doesn't create the Image models with the data upon saving (and doesn't feel right):
params[:post][:images] = JSON.parse(params[:post][:images])
All of this essentially culminates to something like the following parameters:
{"post": {"title": "", "content": "", ..., "images": [{ "public_id": "", "bytes": 12345, "format": "jpg"}, { ..another image ... }]}}
This whole process seems a little convoluted -- What do I do now, and is there a better way to do what I'm trying to do in the first place? (Also are strong parameters required for nested attributes like this...?)
EDIT:
At this point I got this error:
Image(#91891690) expected, got ActionController::Parameters(#83350730)
coming from this line...
#post = current_user.reviews.new(post_params)
Seems like it's not creating the images from the nested attributes but it's expected to.
(The same thing happens when :autosave is there or not).
Just had this issue with that ActionController::Parameters error. You need to make sure you're permitting all the necessary parameters in your posts_controller, like so:
def post_params
params.fetch(:post).permit(:title, :content,
images_attributes: [:id, :public_id, :bytes, :format])
end
It's important to make sure you're permitting the image.id attribute.
You must build the params like this:
params[:post][:images_attributes] = { ... }
You need *_attributes on a key name of images.
The accepts_nested_attributes_for should care of this for you. So doing a Post.create(params[:post]) should also take care of the nested image attributes. What might be going wrong is that you have not specified an autosave on the has_many relationship. So you might want to see if this makes a difference:
has_many :images, :dependent => :destroy, :autosave => true
That should save the images too when you save your post.

How can you update/destroy one embed document that is inside of all documents on a collection

My application model allows Patients to have CustomFields. All patients have the same customs fields. Customs fields are embedded in the Patient document. I should be able to add, update and remove custom fields and such actions are extended to all patients.
class Patient
include Mongoid::Document
embeds_many :custom_fields, as: :customizable_field
def self.add_custom_field_to_all_patients(custom_field)
Patient.all.add_to_set(:custom_fields, custom_field.as_document)
end
def self.update_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.update_custom_field(custom_field) }
end
def update_custom_field(custom_field)
self.custom_fields.find(custom_field).update_attributes({ name: custom_field.name, show_on_table: custom_field.show_on_table } )
end
def self.destroy_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.remove_custom_field(custom_field) }
end
def remove_custom_field(custom_field)
self.custom_fields.find(custom_field).destroy
end
end
class CustomField
include Mongoid::Document
field :name, type: String
field :model, type: Symbol
field :value, type: String
field :show_on_table, type: Boolean, default: false
embedded_in :customizable_field, polymorphic: true
end
All pacients have the same customs fields embedded in. Adding a custom field works very well. My doubt is about updating and destroying.
This works, but it is slow. It makes a query for each pacient. Ideally I would just be able to say to MongoDB 'update the document with id: that is embedded in the array *custom_fields* for all documents in the Patient collection'. Idem for destroy.
How can I do this in Mongoid?
I am using Mongoid 3.1.0 & Rails 3.2.12
I don't think there is a way you can do that with a good efficiency with embedded documents.
Maybe you should consider having a referenced relationship between your models, so that you can use the delete_all and update_all methods on the collection.

rails3-jquery-autocomplete passing parameter to scope query

I'm struggling to find an answer to a problem I have with the rails3-jquery-autocomplete gem.
I have 2 models. Environment & Property.
class Environment < ActiveRecord::Base
has_many :properties
name: string, envfile: string
class Property < ActiveRecord::Base
scope :with_environment_id, where(:environment_id => 14)
belongs_to :environment
name: string, value: string, environment_id: integer
I have a view which displays all the properties belonging to a particular environment and in that view the following code which auto searches based on the property name
<%= form_tag('tbd')%>
<%= autocomplete_field_tag :property_name, '', autocomplete_property_name_properties_path>
The properties controller
class PropertiesController < ApplicationController
autocomplete :property, :name, :scopes => [:with_environment_id]
and routes.rb has
get :autocomplete_property_name, :on => :collection
The autocomplete works fine but it returns returns all records in properties table. Where I would like only the properties belonging to the environment displayed in the view.
As you can see I've defined a scope in the property model in which I've hardcoded a valid environment_id to make this work as I would want it.
So my question is how do I pass the environment_id from the view/controller back into the scope?
Perhaps I'm tackling this from the wrong angle.
Apologies if it's a dumb question but it has stumped me all day.
def get_autocomplete_items(parameters)
super(parameters).where(:user_id => current_user.id)
end

Mongo / Mongoid will create, but not update a model

I have an app which updates a post if it exists, otherwise it creates a new one. This post contains embedded documents:
class Post
embeds_one :tag, :as => :taggable, :class_name => 'TagSnippet'
end
class TagSnippet
include Mongoid::Document
field :name
embedded_in :taggable, polymorphic: true
end
The post is updated in a controller with the following code:
#post = Post.where(--some criteria which work--).first
if #post
#post.attributes = params
else
#post = Post.new(params)
end
#post.save!
This code runs and updates the non-embedded documents, but does not update the embedded docs. Oddly, when I debug in Rubymine, all the attributes of the #post change appropriately (including the embedded ones), but regardless the database does not get updated.
This indicates to me it's some mongo or mongoid problem, but rolling back mongo and mongoid gems produced no change.
I guess that your embedded document is defined like this:
field :subdoc, type: Hash
I bumped into this a couple of times already. Short explanation: Mongoid doesn't track changes inside subhashes.
doc.subdoc.field_a = 1 # won't be tracked
sd = doc.subdoc.dup
sd.field_a = 1
doc.subdoc = sd # should be tracked
So, if Mongoid doesn't detect assignments, it doesn't mark attribute dirty, and therefore doesn't include it in the update operation.
Check this theory by printing doc.subdoc_changed? before saving.

Resources