I have parent and child models on rails 5 with mongoid. When I query the parent, with .includes command - I can see rails trying to query mongo db - but the result json does not return the child objects.
Parent Model:
class Activity
include Mongoid::Document
field :title, type: String
has_many :activity_pictures
end
Child Model:
class ActivityPicture
include Mongoid::Document
field :name, type: String
belongs_to :activity, :class_name => 'Activity'
end
The controller methods:
def index
#activities = Activity.includes(:activity_pictures).all
end
def show
Activity.includes(:activity_pictures)
end
off course, I have updated activity_params:
def activity_params
params.require(:activity).permit(:title, :activity_pictures)
end
How do i get the full json data from http://localhost:3000/activities.json and the single object links?
Whilst the associations are being loaded with the use of includes, you need to specifically call the loaded association in order for it to render. Try
def index
#activities = Activity.includes(:activity_pictures).all
render json: #activities, include :activity_pictures
end
The answer above by margo was the right lead. I am using jbuilder though, so the solution was to change the file
index.json.jbuilder
as follows:
json.array! #activities do |activity|
json.title activity.title
json.activity_pictures activity.activity_pictures do |activity_picture|
json.name activity_picture.name
end
end
Related
I'm updating the API code to FastJson (https://github.com/Netflix/fast_jsonapi) in where I work. The "old" code is using ActiveModel and has
ActiveModel::Serializer::CollectionSerializer.new. I have no idea how to "translate" this code to FastJson API.
I already searched in FastJson documentation about Collection Serialization (https://github.com/Netflix/fast_jsonapi#collection-serialization), but i didnt understand the example.
class API::Messages::MessagesSerializer < ActiveModel::Serializer
attributes :id, :name, :description
attribute :chats do
ActiveModel::Serializer::CollectionSerializer.new(
object.user_chats, serializer: API:Messages::ChatUserSerializer
)
end
end
When collection will be passed in serializer it'll deal that collection perfectly no need to configure anything extra. Following are the serializers
class API::Messages::MessagesSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name, :description
attributes :chats do |message|
API::Messages::ChatsSerializer.new(message. user_chats)
end
end
class API::Messages::ChatsSerializer
include FastJsonapi::ObjectSerializer
attributes ... # add attribute/logic as you want for single chat object
end
And your controller will be something like this
def show
render json: API::Messages::MessagesSerializer.new(#message).serialized_json, status: :ok
end
I m using relation ( has_many , belongs_to) tha's working with source code above here, but I get error document Not Found for delete Photo(picture) when change relations (embeds_many, embedded_in). Anybody Helpme please, how to use Embed_many relations using mongoid & what's wrong my source code here :
class Room
include Mongoid::Document
field :home_type, type: String
field :room_type, type: String
embeds_many :photos
end
class Photo
include Mongoid::Document
include Mongoid::Paperclip
embedded_in :room
end
class PhotosController < ApplicationController
def destroy
#photo = Photo.find(params[:id])
room = #photo.room
#photo.destroy
#photos = Photo.where(room_id: room.id)
respond_to :js
end
end
The simple answer here is that when you embed a document you are adding that document within another one. In order for mongodb to find the embedded document it first needs to find the parent. In your previous iteration you with has_many you are associating two documents from different collections, with enables you to look up by the associated document.
Therefore although embedded documents have an _id, you are only able to look them up from within the document. If you were to output #photo you would see that it was nil. I am surprised that your second line room = #photo.room is not returning an error no method for nil:NilClass.
To do what you want, you first need to find the document, which you could do without too much change:
class PhotosController < ApplicationController
def destroy
room = Room.find_by('photo._id': BSON::ObjectId(params[:id]))
#photo = room.photos.find(params[:id])
#photo.destroy
#photos = room.photos
respond_to :js
end
end
I had a model which returns some parameters and includes parameters from other models as follows:
def as_json(options = {})
camelize_keys(super(options.merge(:only => [:id, :userId], include:{
comments: { only: [:test, :id] },
valediction: { only: [:name, :text, :hidden, :order] }
})))
end
def camelize_keys(hash)
values = hash.map do |key, value|
[key.camelize(:lower), value]
end
Hash[values]
end
Now I have moved the code to my controller because different controller actions need to return different parts of the model. (index should just return valediction, but show should return comments and valediction)
The new controller:
def index
respond_with(displayed_user.microposts.all, include: {
valediction: { only: [:name, :text] }
})
end
def show
respond_with(displayed_user.microposts.find(params[:id]), include: {
comments: { only: [:test, :id] },
valediction: { only: [:name, :text, :hidden, :order] }
})
end
But I'm very new to rails and I don't know how to put the camelize_keys function in so that it works.
Doing complex JSON formatting in your controllers / and or models usually leads to bloat and is a pain to test.
A good solution for this is using the ActiveModel::Serializer (AMS) gem. Its included in Rails 5 but you can easily add it to a Rails 4 project by adding it to the gemfile:
# See rubygems.org for latest verstion!
gem 'active_model_serializers', '~> 0.9.3'
Then run bundle install and restart your rails server.
With AMS you create serializer classes which define how your model data should be represented in JSON, XML etc. A serializer is basically a class that takes a model instance (or an array of models) and returns a hash (or an array of hashes) when you call .serializable_hash.
But Rails will take care of that part automatically for you.
class MicropostSerializer < ActiveModel::Serializer
attributes :id, :user_id
has_many :comments
has_many :valedictions
end
class CommentSerializer < ActiveModel::Serializer
attributes :test, :id
end
class ValedictionSerializer < ActiveModel::Serializer
attributes :name, :text, :hidden, :order
end
In your controller you can simply call:
def index
render json: displayed_user.microposts.all
end
But wait, what about camelize_keys?
Unless you have to support some weird legacy client that needs camelized keys there are very few reasons to do this. Most large API's use snakecase (Facebook, Google etc.) and Rails 5 is moving towards the JSONAPI spec which uses snakecase.
From your code sample it seems that some of your rails model attributes (and the db columns backing them) use camelcase. You should change the DB column with a migration as soon as possible.
If you HAVE to support a legacy database you can use alias_attribute:
class Pet < ActiveRecord::Base
alias_attribute :ownerId, :owner_id
end
You could move the method to a class method in the model, eg
#class methods
class << self
def camelize_keys(hash)
values = hash.map do |key, value|
[key.camelize(:lower), value]
end
Hash[values]
end
end
Now you can call this from anywhere like
MyModel.camelize_keys(some_hash)
I have a couple models shown below and I'm using the search class method in Thing to filter records
class Category << ActiveRecord::Base
has_many :thing
end
class Thing << ActiveRecord::Base
belongs_to :category
:scope approved -> { where("approved = true") }
def self.search(query)
search_condition = "%" + query + "%"
approved.where('name LIKE ?', search_condition)
end
end
It works fine in my Things controller. The index route looks like so:
def index
if params[:search].present?
#things = Thing.search(params[:seach])
else
#thing = Thing.all
end
end
On the categories show route I display the Things for this category. I also have the search form to search within the category.
def show
#category = Categories.find(params[:id])
if params[:search].present?
#category.things = #category.things.search()
end
end
So the problem is that the category_id attribute of all the filtered things are getting set to nil when I use the search class method in the categories#show route. Why does it save it to database? I thought I would have to call #category.save or update_attribute for that. I'm still new to rails so I'm sure its something easy I'm overlooking or misread.
My current solution is to move the if statement to the view. But now I'm trying to add pages with kaminiri to it and its getting uglier.
<% if params[:search].present? %>
<% #category.things.search(params[:search]) do |thing| %>
... Show the filtered things!
<% end %>
<% else %>
<% #category.things do |thing| %>
... Show all the things!
<% end %>
<% end %>
The other solution I thought of was using an #things = #categories.things.search(params[:search]) but that means I'm duplicated things passed to the view.
Take a look at Rails guide. A has_many association creates a number of methods on the model to which collection=(objects) also belongs. According to the guide:
The collection= method makes the collection contain only the supplied
objects, by adding and deleting as appropriate.
In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id]).
Like Yan said, "In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id])". Validations will solve this problem.
Records are being saved as nil because you have no validations on your model. Read about active record validations.
Here's the example they provide. You want to validate presence as well because records are being created without values.
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
I would like to uniquely use owner tags in my app. My problem is that when I create / update a post via a form I only have f.text_field :tag_list which only updates the tags for the post but has no owner. If I use f.text_field :all_tags_list it doesn't know the attribute on create / update. I could add in my controller:
User.find(:first).tag( #post, :with => params[:post][:tag_list], :on => :tags )
but then I have duplicate tags, for post and for the owner tags. How can I just work with owner tags?
The answer proposed by customersure (tsdbrown on SO) on https://github.com/mbleigh/acts-as-taggable-on/issues/111 works for me
# In a taggable model:
before_save :set_tag_owner
def set_tag_owner
# Set the owner of some tags based on the current tag_list
set_owner_tag_list_on(account, :tags, self.tag_list)
# Clear the list so we don't get duplicate taggings
self.tag_list = nil
end
# In the view:
<%= f.text_field :tag_list, :value => #obj.all_tags_list %>
I used an observer to solve this. Something like:
in /app/models/tagging_observer.rb
class TaggingObserver < ActiveRecord::Observer
observe ActsAsTaggableOn::Tagging
def before_save(tagging)
tagging.tagger = tagging.taggable.user if (tagging.taggable.respond_to?(:user) and tagging.tagger != tagging.taggable.user)
end
end
Don't forget to declare your observer in application.rb
config.active_record.observers = :tagging_observer
Late to the party, but I found guillaume06's solution worked well, and I added some additional functionality to it:
What this will enable: You will be able to specify the tag owner by the name of the relationship between the tagged model and the tag owner model.
How: write a module and include in your lib on initialization (require 'lib/path/to/tagger'):
module Giga::Tagger
extend ActiveSupport::Concern
included do
def self.tagger owner
before_save :set_tag_owner
def set_tag_owner
self.tag_types.each do |tag|
tag_type = tag.to_s
# Set the owner of some tags based on the current tag_list
set_owner_tag_list_on(owner, :"#{tag_type}", self.send(:"#{tag_type.chop}_list"))
# Clear the list so we don't get duplicate taggings
self.send(:"#{tag_type.chop}_list=",nil)
end
end
end
end
end
Usage Instructions:
Given: A model, Post, that is taggable
A model, User, that is the tag owner
A post is owned by the user through a relationship called :owner
Then add to Post.rb:
include Tagger
acts_as_taggable_on :skills, :interests, :tags
tagger :owner
Make sure Post.rb already has called acts_as_taggable_on, and that User.rb has acts_as_tagger
Note: This supports multiple tag contexts, not just tags (eg skills, interests)..
the set_tag_owner before_save worked for me. But as bcb mentioned, I had to add a condition (tag_list_changed?) to prevent the tags from being deleted on update:
def set_tag_owner
if tag_list_changed?
set_owner_tag_list_on(account, :tags, tag_list)
self.tag_list = nil
end
end
When working with ownership the taggable model gets its tags a little different. Without ownership it can get its tags like so:
#photo.tag_list << 'a tag' # adds a tag to the existing list
#photo.tag_list = 'a tag' # sets 'a tag' to be the tag of the #post
However, both of these opperations create taggins, whose tagger_id and tagger_type are nil.
In order to have these fields set, you have to use this method:
#user.tag(#photo, on: :tags, with: 'a tag')
Suppose you add this line to the create/update actions of your PhotosController:
#user.tag(#photo, on: :tags, with: params[:photo][:tag_list])
This will create two taggings (one with and one without tagger_id/_type), because params[:photo][:tag_list] is already included in photo_params. So in order to avoid that, just do not whitelist :tag_list.
For Rails 3 - remove :tag_list from attr_accessible.
For Rails 4 - remove :tag_list from params.require(:photo).permit(:tag_list).
At the end your create action might look like this:
def create
#photo = Photo.new(photo_params) # at this point #photo will not have any tags, because :tag_list is not whitelisted
current_user.tag(#photo, on: :tags, with: params[:photo][:tag_list])
if #photo.save
redirect_to #photo
else
render :new
end
end
Also note that when tagging objects this way you cannot use the usual tag_list method to retrieve the tags of a photo, because it searches for taggings, where tagger_id IS NULL. You have to use instead
#photo.tags_from(#user)
In case your taggable object belongs_to a single user you can also user all_tags_list.
Try using delegation:
class User < ActiveRecord::Base
acts_as_taggable_on
end
class Post < ActiveRecord::Base
delegate :tag_list, :tag_list=, :to => :user
end
So when you save your posts it sets the tag on the user object directly.
I ended up creating a virtual attribute that runs the User.tag statement:
In my thing.rb Model:
attr_accessible :tags
belongs_to :user
acts_as_taggable
def tags
self.all_tags_list
end
def tags=(tags)
user = User.find(self.user_id)
user.tag(self, :with => tags, :on => :tags, :skip_save => true)
end
The only thing you have to do is then change your views and controllers to update the tag_list to tags and make sure you set the user_id of the thing before the tags of the thing.