Rails 3: As json with include option does not takes into account as_json redefinition for included association - ruby-on-rails-3

I've got two models.
Class ModelA < ActiveRecord::Base
has_many :model_bs
end
Class ModelB < ActiveRecord::Base
belongs_to :model_a
def as_json(options = {})
{
:whatever => 'hello world'
}
end
end
When I call model_a.as_json(:include => :model_b), I want it to return a json which includes all model_bs, which it does, but employing my as_json redefinition, which it does not as it just uses the default one. Is there any way to use my own method rather than the original one? Thanks

In Rails 3, as_json method invokes serializable_hash to obtain the attributes hash. And they share the same 'options' parameter. In your case, overwritting serializable_hash would give the expected result.
def serializable_hash(options = {})
{:whatever => 'hello world'}
end
But, My suggestion is that instead of overwriting the convention, operate on the result of "super", which is like:
def serializable_hash(options = {})
hash = super
has[:name] = "hello world"
hash
end

Related

Rails - Serialize related data

I've got two models:
class Continent < ActiveRecord::Base
has_many :countries
end
class Country < ActiveRecord::Base
belongs_to :continent
end
I created controller like:
class ContinentsController < ApplicationController
def index
#continents = Continent.all
render json: #continents
end
end
and serializer:
class ContitnentSerializer < ActiveModel::Serializer
attributes :name, :countries
end
Here my issue begins. I'd like to serialize only countries with given condition where value comes from HTTP GET params. E.g country inside serializer should be displayed only if population is more than params[:population]. The problem is inside serializer we don't have access to params to examine that.
[
{
name: 'Europe'
countries: [
{
name: 'Italy',
population: 1000000
}
]
},
{
name: 'Africa'
countries: [
]
}
]
I've tried to join table with condition but it seems be not working.
#continents = Continent.all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > #{params[:population]}")
Create a scope and call the scope with param value from controller:
scope :population_more_than, ->(population) {all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > ?", population)}
Now call it from controller instead of Continent.all
Continent.population_more_than(params[:population])
You can try
#continents = Continent.all
#continents.num_population = params[:population]
render json: #continents.to_json(methods: :countries_with_population_gt)
in your Continent model
attr_accessor :num_population
def countries_with_population_gt(num_population=0)
countries.where('population > ?', #num_population)
end
Basically, you need to select only Continents that fall under specific rule. If this is a frequently used filter, then I would go with the Babar's suggestion and create a scope.
If this is a one time selection, then I prefer simply do filtering right there without cluttering up my models with non-frequently used scopes.
Continent.joins(:countries).where("countries.population > :population", population: params[:population])
# Or event shorter
Continent.joins(:countries).where("countries.population > :population", params)

Rails form to edit JSON object as text

I'd like to make a form that lets a user edit one field of a mongoid object as rendered JSON text. There's a field in the model that my rails app should not understand, but I want to expose a generic editor. So for this field, I'd like to render it as pretty JSON, and expose it in a big <textarea> and then parse the JSON back in after any edits.
I can think of a dozen ways to do this, but I'm wonder what would be most consistent with Rails philosophy and least divergent from normal scaffolding. Should I render the object to JSON text in the controller? Then I'd have to repeat that code in the new and edit methods, and the parsing code in the update and create methods, which seems a bit kludgy. Is there a way to define a helper or custom form widget that goes in the _form.html.erb that is more reusable? Or maybe one already written?
You can make your own attribute writer/reader, in the model:
attr_accessible the_field_raw
def the_field_raw
self.the_field.to_s
end
def the_field_raw=(value)
self.the_field = JSON(value)
end
whitch should be compatible with form generators and no extra code in the controllers.
Hope it helps!
Serialize the values as JSON.
class Price < ActiveRecord::Base
serialize :values, JSON
validates :start, :end, :values, :presence => true
end
migration:
class CreateMyModels < ActiveRecord::Migration[7.0]
def change
create_table :my_models do |t|
t.jsonb :name, default: {}, null: false
t.jsonb :description, default: {}, null: false
t.integer :another_param
t.timestamps
end
end
end
model and concern:
class MyModel < ApplicationRecord
AVAILABLE_LOCALES = I18n.available_locales
include JsonLocalize
json_localize :name, :description
end
module JsonLocalize
extend ActiveSupport::Concern
included do
def self.json_localize(*attrs)
self::AVAILABLE_LOCALES.each do |locale|
attrs.each do |attr|
define_method("#{attr}_#{locale}") do
send(attr)[locale.to_s]
end
define_method("#{attr}_#{locale}=") do |value|
send(attr)[locale.to_s] = value
end
end
end
end
end
end
then you can have in your form:
.row
.col-md-6
- MyModel::AVAILABLE_LOCALES.each do |loc|
= f.input "name_#{loc}"
= f.input "description_#{loc}"
controller params:
def resource_params
params.require(:my_model).permit(
[
:another_param
] | [:name, :description].map {|attr| MyModel::AVAILABLE_LOCALES.map { |loc| "#{attr}_#{loc}".to_sym } }.flatten
)
end

Rails 3: Find parent of polymorphic model in controller?

I'm trying to find an elegant (standard) way to pass the parent of a polymorphic model on to the view. For example:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
The following way (find_imageable) works, but it seems "hackish".
#PictureController (updated to include full listing)
class PictureController < ApplicationController
#/employees/:id/picture/new
#/products/:id/picture/new
def new
#picture = imageable.pictures.new
respond_with [imageable, #picture]
end
private
def imageable
#imageable ||= find_imageable
end
def find_imageable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
Is there a better way?
EDIT
I'm doing a new action. The path takes the form of parent_model/:id/picture/new and params include the parent id (employee_id or product_id).
I'm not sure exactly what you're trying to do but if you're trying to find the object that 'owns' the picture you should be able to use the imageable_type field to get the class name. You don't even need a helper method for this, just
def show
#picture = Picture.find(params[:id])
#parent = #picture.imagable
#=> so on and so forth
end
Update
For an index action you could do
def index
#pictures = Picture.includes(:imagable).all
end
That will instantiate all 'imagables' for you.
Update II: The Wrath of Poly
For your new method you could just pass the id to your constructor, but if you want to instantiate the parent you could get it from the url like
def parent
#parent ||= %w(employee product).find {|p| request.path.split('/').include? p }
end
def parent_class
parent.classify.constantize
end
def imageable
#imageable ||= parent_class.find(params["#{parent}_id"])
end
You could of course define a constant in your controller that contained the possible parents and use that instead of listing them in the method explicitly. Using the request path object feels a little more 'Rails-y' to me.
I just ran into this same problem.
The way I 'sort of' solved it is defining a find_parent method in each model with polymorphic associations.
class Polymorphic1 < ActiveRecord::Base
belongs_to :parent1, :polymorphic => true
def find_parent
self.parent1
end
end
class Polymorphic2 < ActiveRecord::Base
belongs_to :parent2, :polymorphic => true
def find_parent
self.parent2
end
end
Unfortunately, I can not think of a better way. Hope this helps a bit for you.
This is the way I did it for multiple nested resources, where the last param is the polymorphic model we are dealing with: (only slightly different from your own)
def find_noteable
#possibilities = []
params.each do |name, value|
if name =~ /(.+)_id$/
#possibilities.push $1.classify.constantize.find(value)
end
end
return #possibilities.last
end
Then in the view, something like this:
<% # Don't think this was needed: #possibilities << picture %>
<%= link_to polymorphic_path(#possibilities.map {|p| p}) do %>
The reason for returning the last of that array is to allow finding the child/poly records in question i.e. #employee.pictures or #product.pictures

Rails assigning names to variables

I'm building a user ranking system, and am trying to assign user.rank values with a name.
I wanted to define something like this in my User model and then be able to reference it when displaying each user's rank, but this probably isn't the best way:
class User < ActiveRecord::Base
RANK_NAMES = {
'Peasant' => (0..75),
'Craftsman' => (76..250),
'Vassal' => (251..750),
'Noble' => (750..1500),
'Monarch' => (1501..999999)
}
Perhaps it would be better to define a method in a controller or helper like:
if user.rank == 0..75
rank_name = "Peasant"
elsif...
But not sure how to do that. Anyone have any thoughts? I'm not even sure what to call what it is I'm trying to do, thus making it difficult to research on my own.
It could be something even as simple as this, assuming user.rank exists.
class User < ActiveRecord::Base
...
def rank_name
case self.rank
when 0..75
'Peasant'
when 76..250
'Craftsman'
when 251..750
'Vassal'
when 750..1500
'Noble'
when 1501..999999
'Monarch'
end
end
...
end
If rank_name is specific to the User, I'd make it a method of User.
You could try something like below. It might give you some ideas.
class User
RANKS = [
{:name => 'Peasant', :min => 0, :max => 75},
{:name => 'Craftsman', :min => 76, :max => 250}
# ...
]
attr_accessor :rank
def rank_name
# TODO what happens if rank is out of range of all ranks or rank is nil
# or not an integer
User::RANKS[rank_index][:name]
end
private
def rank_index
User::RANKS.index { |r| (r[:min]..r[:max]).include? #rank }
end
end
user = User.new
user.rank = 76
puts user.rank_name # -> Craftsman

override to_xml to limit fields returned

using ruby 1.9.2 and rails 3, i would like to limit the fields returned when a record is accessed as json or xml (the only two formats allowed).
this very useful post introduced me to respond_with and i found somewhere online that a nice way to blanket allow/deny some fields is to override as_json or to_xml for the class and set :only or :except to limit fields.
example:
class Widget < ActiveRecord::Base
def as_json(options={})
super(:except => [:created_at, :updated_at])
end
def to_xml(options={})
super(:except => [:created_at, :updated_at])
end
end
class WidgetsController < ApplicationController
respond_to :json, :xml
def index
respond_with(#widgets = Widgets.all)
end
def show
respond_with(#widget = Widget.find(params[:id]))
end
end
this is exactly what i am looking for and works for json, but for xml "index" (GET /widgets.xml) it responds with an empty Widget array. if i remove the to_xml override i get the expected results. am i doing something wrong, and/or why does the Widgets.to_xml override affect the Array.to_xml result?
i can work around this by using
respond_with(#widgets = Widgets.all, :except => [:created_at, :updated_at])
but do not feel that is a very DRY method.
In your to_xml method, do the following:
def to_xml(options={})
options.merge!(:except => [:created_at, :updated_at])
super(options)
end
That should fix you up.