Anyone know why some of my json elements are being backslash(\) escaped while others are not?
{"first":"John","last":"Smith","dogs":"[{\"name\":\"Rex\",\"breed\":\"Lab\"},{\"name\":\"Spot\",\"breed\":\"Dalmation\"},{\"name\":\"Fido\",\"breed\":\"Terrier\"}]"}
Ideally I'd like NONE of them to be escaped...
This was generated by overriding as_json in two models. Person has_many Dogs.
#models/person.rb
class Person < ActiveRecord::Base
has_many :dogs
def as_json(options={})
{
:first => first,
:last => last,
:dogs => dogs.to_json
}
end
end
#models/dog.rb
class Dog < ActiveRecord::Base
belongs_to :people
def as_json(options={})
{
:name => name,
:breed => breed
}
end
end
Check out jonathanjulian.com's Rails to_json or as_json?
Try removing the to_json on dogs.to_json.
Related
I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails.
We end up with something like
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
end
Now, if two of those subjects are for example:
class Event < ActiveRecord::Base
has_many :guests
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
class Image < ActiveRecord::Base
has_many :tags
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
With create_activities defined as
def create_activities
Activity.create(subject: self)
end
And with guests and tags defined as:
class Guest < ActiveRecord::Base
belongs_to :event
end
class Tag < ActiveRecord::Base
belongs_to :image
end
If we query the last 20 activities logged, we can do:
Activity.order(created_at: :desc).limit(20)
We have a first N+1 query issue that we can solve with:
Activity.includes(:subject).order(created_at: :desc).limit(20)
But then, when we call guests or tags, we have another N+1 query problem.
What's the proper way to solve that in order to be able to use pagination ?
Edit 2: I'm now using rails 4.2 and eager loading polymorphism is now a feature :)
Edit: This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. I need to investigate...
Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types.
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id
belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id
end
And now you can do
Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10)
But for eager loading to work, you must use for example
activity.event.guests.first
and not
activity.part.guests.first
So you can probably define a method to use instead of subject
def eager_loaded_subject
public_send(subject.class.to_s.underscore)
end
So now you can have a view with
render partial: :subject, collection: activity
A partial with
# _activity.html.erb
render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject
And two (dummy) partials
# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
This will hopefully be fixed in rails 5.0. There is already an issue and a pull request for it.
https://github.com/rails/rails/pull/17479
https://github.com/rails/rails/issues/8005
I have forked rails and applied the patch to 4.2-stable and it works for me. Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis.
https://github.com/ttosch/rails/tree/4-2-stable
You can use ActiveRecord::Associations::Preloader to preload guests and tags linked, respectively, to each of the event and image objects that are associated as a subject with the collection of activities.
class ActivitiesController < ApplicationController
def index
activities = current_user.activities.page(:page)
#activities = Activities::PreloadForIndex.new(activities).run
end
end
class Activities::PreloadForIndex
def initialize(activities)
#activities = activities
end
def run
preload_for event(activities), subject: :guests
preload_for image(activities), subject: :tags
activities
end
private
def preload_for(activities, associations)
ActiveRecord::Associations::Preloader.new.preload(activities, associations)
end
def event(activities)
activities.select &:event?
end
def image(activities)
activities.select &:image?
end
end
image_activities = Activity.where(:subject_type => 'Image').includes(:subject => :tags).order(created_at: :desc).limit(20)
event_activities = Activity.where(:subject_type => 'Event').includes(:subject => :guests).order(created_at: :desc).limit(20)
activities = (image_activities + event_activities).sort_by(&:created_at).reverse.first(20)
I would suggest adding the polymorphic association to your Event and Guest models.
polymorphic doc
class Event < ActiveRecord::Base
has_many :guests
has_many :subjects
after_create :create_activities
end
class Image < ActiveRecord::Base
has_many :tags
has_many :subjects
after_create :create_activities
end
and then try doing
Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)
Does this generate a valid SQL query or does it fail because events can't be JOINed with tags and images can't be JOINed with guests?
class Activity < ActiveRecord::Base
self.per_page = 10
def self.feed
includes(subject: [:guests, :tags]).order(created_at: :desc)
end
end
# in the controller
Activity.feed.paginate(page: params[:page])
This would use will_paginate.
In my Rails app, I have a has_many through relationship between two models and therefore I am creating new objects like this:
Project.new(:name => 'Test', :person_ids => [1, 2, 3])
What is a good way to validate those person_ids in the model?
This is what I have so far:
class Project < ActiveRecord::Base
has_many :people_projects
has_many :people, :through => :people_projects
validates :person_ids, inclusion => { :in => lambda { |x| x.valid_people } }
def valid_people
user.people.map(&:id)
end
end
However, this doesn't work because the person_ids get posted in an array.
Can anybody help?
Use a custom validation method like
class Project < ActiveRecord::Base
validate :valid_people
def valid_people
people = user.people.pluck(:id)
if person_ids.blank? || (person_ids - people).any?
errors.add(:person_ids, "Please add real people")
end
end
end
I have a teacher model which has_many students. When I render a student (as json) I want to strip out the teacher_id property and replace it with the name of the teacher in my representation.
What is the best way to achieve this?
Cheers,
Chris
Define a method called teacher_name.
class Student < ActiveRecord::Base
def teacher_name
self.teacher.name
end
end
Include the teacher_name method while invoking to_json:
teacher.to_json(:include => {:students => {:methods=> :teacher_name,
:except => :teacher_id
}
}
)
You can always redefine the to_json method on the model to do whatever you want:
class Student < ActiveRecord::Base
def to_json
self.attributes.merge(
'teacher_id' => self.teacher.name
).to_json
end
end
Update: Based on feedback from kandadaboggu , using a different approach:
class Student < ActiveRecord::Base
def teacher_name
self.teacher and self.teacher.name
end
def to_json(options = { })
super(
{
:methods => :teacher_name,
:except => :teacher_id)
}.merge(options)
)
end
end
Note that when using a brute-force merge like this the result is sometimes less than satisfactory, but will serve for the default case. If you specify :methods or :except options of your own, the defaults will be ignored.
My Example:
class Category < ActiveRecord::Base
has_many :tags, :as => :tagable, :dependent => :destroy
def tag_string
str = ''
tags.each_with_index do |t, i|
str+= i > 0 ? ', ' : ''
str+= t.tag
end
str
end
def tag_string=(str)
tags.delete_all
Tag.parse_string(str).each { |t| tags.build(:tag => t.strip) }
end
end
How would you optimize this tag_string field? I don't want to delete all the tags every time I would like to just update them. Is there a better way to parse string with tags?
I do not want use a plugin! Thx.
I know you don't want to use a plugin, but you might want to dig through the source of acts_as_taggable_on_steroids to see how they're handling these situations. From my experience, working with that plugin has been very painless.
class Category < ActiveRecord::Base
has_many :tags, :as => :tagable, :dependent => :destroy
def tag_string
tags.map {|t| t.name }.join ', '
end
def tag_string=(str)
tags = Tag.parse_string(str)
end
end
I don't know what Tag.parse_string(str) method do. If it returns an array of Tag objects, than my example should work. And I'm not sure if this will only update, or delete old and add new one. You can test it and look in logs what it really does.
I agree with other commenters. You are better off using the plugin here. Here is one solution.
class Category < ActiveRecord::Base
has_many :tags, :as => :tagable, :dependent => :destroy
def tag_string
tags.collect(&:name).join(", ")
end
def tag_string=(str)
# Next line will delete the old association and create
# new(based on the passed str).
# If the Category is new, then save it after the call.
tags = Tag.create(str.split(",").collect{ |name| {:name => name.strip} })
end
end
I am trying to calculate the average (mean) rating for all entries within a category based on the following model associations ...
class Entry < ActiveRecord::Base
acts_as_rateable
belongs_to :category
...
end
class Category < ActiveRecord::Base
has_many :entry
...
end
class Rating < ActiveRecord::Base
belongs_to :rateable, :polymorphic => true
...
end
The rating model is handled by the acts as rateable plugin, so the rateable model looks like this ...
module Rateable #:nodoc:
...
module ClassMethods
def acts_as_rateable
has_many :ratings, :as => :rateable, :dependent => :destroy
...
end
end
...
end
How can I perform the average calculation? Can this be accomplished through the rails model associations or do I have to resort to a SQL query?
The average method is probably what you're looking for. Here's how to use it in your situation:
#category.entries.average('ratings.rating', :joins => :ratings)
Could you use a named_scope or custom method on the model. Either way it would still require some SQL since, if I understand the question, your are calculating a value.
In a traditional database application this would be a view on the data tables.
So in this context you might do something like... (note not tested or sure it is 100% complete)
class Category
has_many :entry do
def avg_rating()
#entries = find :all
#entres.each do |en|
#value += en.rating
end
return #value / entries.count
end
end
Edit - Check out EmFi's revised answer.
I make no promises but try this
class Category
def average_rating
Rating.average :rating,
:conditions => [ "type = ? AND entries.category_id = ?", "Entry", id ],
:join => "JOIN entries ON rateable_id = entries.id"
end
end