I am using Rails 3 and here is my model
class LineItem < ActiveRecord::Base
attr_reader :price
belongs_to :product
def price
self.product.price * self.quantity
end
def as_json(options = {})
super(:include => [:product])
end
end
Above code works. However now I want my json to also have value for price in addition to the other values that I am getting.
How do I accomplish that?
You can use :methods:
def as_json(options = {})
super(options.merge(:include => [:product], :methods => [:price]))
end
You might want to pay proper attention to any incoming :include and :method settings in your options though. So you might want to use the block form of merge:
EXTRAS = { :include => [:product], :methods => [:price] }
def as_json(options = { })
super(options.merge(EXTRAS) { |k,ov,nv| ov.is_a?(Array) ? ov + nv : nv }
end
Related
I have a index view in my rails application that allows filtering via search params. When a group op articles are returned its is wropped in an articles colllection like {"articles":[{"article":{"id":341,"updated":"2015-08-18T13:05:08.427Z","title":". But if only a single object is found the articles level is missing, {"article":{"id":398,"updated":"2015-08-07T11:37:26.200Z","title":. How can I fix it so that a single object behaves like multiple?
_articles.list.json.jbuilder
require 'uri'
require 'publish_on'
json.cache! ['v1', articles] do
json.articles articles do |article|
json.cache! ['v1', article] do
json.article do
json.id article.id
json.updated as_ns_date(article.updated_at)
json.title article.label
json.numberOfViews article.view_mappings.count
json.numberOfFavorites article.favorite_mappings.count
json.imageURLs article.images if article.images.any?
json.youtubeURL article.youtube unless article.youtube.blank?
json.tags article.categories.map(&:label)
json.isFeatured article.featured
json.isPublished article.is_published
json.published as_ns_date(article.publish_on)
end
end
end
end
index.json.jbuilder
json.partial! 'articles/articles_list', articles: #articles
articles_controller.rb
def index
#articles = SearchArticlesCommand.new(params).execute
render :index
end
search_articles_command.rb
class SearchArticlesCommand
def initialize(params = {})
#since = params[:since_date]
#keys = params[:search_query]
#category = params[:category]
end
def execute
Article.unscoped do
query = if #since.present?
Article.article.since_date(#since)
else
Article.published_article
end
query = query.search_by_keywords(#keys) if #keys.present?
query = query.search_by_category(#category) if #category.present?
query.select(:id, :updated_at, :label, :is_published, :featured, :slug, :created_at).order(created_at: :desc)
end
end
end
article.rb
class Article < Comfy::Cms::Page
include PgSearch
include ActionView::Helpers::SanitizeHelper
HOSTNAME = ENV['HOSTNAME'] || Socket.gethostname
has_many :view_mappings, dependent: :destroy
has_many :favorite_mappings, dependent: :destroy
pg_search_scope :search_by_keywords, against: [:content_cache, :label], using: { tsearch: { any_word: true, prefix: true } }
pg_search_scope :search_by_category, associated_against: {
categories: [:label]
}
scope :since_date, -> (date) { where('created_at > ? OR updated_at > ? ', date, date) if date.present? }
scope :folder, -> { where.not(layout_id: ENV['ARTICLE_LAYOUT_ID']) }
scope :published_article, -> { published.article }
scope :article, -> { where(layout_id: ENV['ARTICLE_LAYOUT_ID']) }
It is what i suspected. If you want the same behavior your query should return the same type of object when it finds one or many articles. The problem is that either you are returning an ActiveRecordRelation or a Article object depending on your params.
#articles = Article.all # => ActiveRecordRelation, an array per se
#articles = Article.find(1) # => Article object
When it comes to jbuilder to construct the JSON it checks if it is an array of objects and then wrap the json with a { keyword => array }. WHen it is a single object, it defaults to a single object {article: {}}.
The solution is simple, you can tweak your SearchArticlesCommand to always return an ActiveRecordRelation, even if it finds only one object.
In my rails app i defined a specific JSON-Format in my model:
def as_json(options={})
{
:id => self.id,
:name => self.name + ", " + self.forname
}
end
And in the controller i simply call:
format.json { render json: #patients}
So now im trying to define another JSON-Format for a different action but i dont know how?
How do i have to define another as_json or how can i pass variables to as_json? Thanks
A very ugly method but you can refactor it for better readability:
def as_json(options={})
if options.empty?
{ :id => self.id, :name => self.name + ", " + self.forname }
else
if options[:include_contact_name].present?
return { id: self.id, contact_name: self.contact.name }
end
end
end
Okay, I should give you a better piece of code, here it is:
def as_json(options = {})
if options.empty?
self.default_json
else
json = self.default_json
json.merge!({ contact: { name: contact.name } }) if options[:include_contact].present?
json.merge!({ admin: self.is_admin? }) if options[:display_if_admin].present?
json.merge!({ name: self.name, forname: self.forname }) if options[:split_name].present?
# etc etc etc.
return json
end
end
def default_json
{ :id => self.id, :name => "#{self.name}, #{self.forname}" }
end
Usage:
format.json { render json: #patients.as_json(include_contact: true) }
By defining hash structure by 'as_json' method, in respective model class i.e User model in (Example 1), it becomes the default hash stucture for active record(i.e., user) in json format. It cannot be overridden by any inline definitions as defined in Example: 2
Example 1:
class User < ActiveRecord::Base
.....
def as_json(options={})
super(only: [:id, :name, :email])
end
end
Example: 2
class UserController < ApplicationController
....
def create
user = User.new(params[:user])
user.save
render json: user.as_json( only: [:id, :name] )
end
end
Therefore, in this example when create action is executed 'user' is returned in ("only: [:id, :name, :email]") format not as ("only: [:id, :name]")
So, options = {} are passed to as_json method to specifiy different format for different methods.
Best Practice, is to define hash structure as constant and call it everwhere it is needed
For Example
Ex: models/user.rb
Here, constant is defined in model class
class User < ActiveRecord::Base
...
...
DEFAULT_USER_FORMAT = { only: [:id, :name, :email] }
CUSTOM_USER_FORMAT = { only: [:id, :name] }
end
Ex: controllers/user.rb
class UserController < ApplicationController
...
def create
...
render json: user.as_json(User::DEFAULT_USER_FORMAT)
end
def edit
...
render json: user.as_json(User::CUSTOM_USER_FORMAT)
end
end
Cool!
Looking for gem or at least idea how to approach this problem, the ones I have are not exactly elegant :)
Idea is simple I would like to map hashes such as:
{ :name => 'foo',
:age => 15,
:job => {
:job_name => 'bar',
:position => 'something'
...
}
}
To objects of classes (with flat member structure) or Struct such as:
class Person {
#name
#age
#job_name
...
}
Thanks all.
Assuming that you can be certain sub-entry keys won't conflict with containing entry keys, here's some code that should work...
require 'ostruct'
def flatten_hash(hash)
hash = hash.dup
hash.entries.each do |k,v|
next unless v.is_a?(Hash)
v = flatten_hash(v)
hash.delete(k)
hash.merge! v
end
hash
end
def flat_struct_from_hash(hash)
hash = flatten_hash(hash)
OpenStruct.new(hash)
end
Solution that I used it solves problem with same key names but it does not give flat class structure. Somebody might find this handy just keep in mind that values with reserved names such as id, type need to be handled.
require 'ostruct'
def to_open_struct(element)
struct = OpenStruct.new
element.each do |k,v|
value = Hash === v ? to_open_struct(v) : v
eval("object.#{k}=value")
end
return struct
end
An alternate answer where you know the keys before hand
class Job
attr_accessor :job_name, :position
def initialize(params = {})
self.job_name = params.fetch(:job_name, nil)
self.position = params.fetch(:position, nil)
end
end
class Person
attr_accessor :name, :age, :job
def initialize(params = {})
self.name = params.fetch(:name, nil)
self.age = params.fetch(:age, nil)
self.job = Job.new(params.fetch(:job, {}))
end
end
hash = { :name => 'foo', :age => 1, :job => { :job_name => 'bar', :position => 'soetmhing' }}
p = Person.new(hash)
p.name
==> "foo"
p.job
==> #<Job:0x96cacd8 #job_name="bar", #position="soetmhing">
p.job.name
==> "bar"
I am new to Rails and just got my category tree working. Now I am not sure if what I have done is "ruby conform" or "ruby-like". I come from PHP and have to change some of my habits but this is not easy. I just want to check if I am on the right way.
The structure is done by the scaffold-command so I guess its correct.
So obvisoulythere is a model-class which is called Category and inherits from ActiveRecord::Base. This model has the following attributes/database fields:
*id,media,image,small_image,clicks,parent,active,description,name,created_at,updated_at*
This is the content of my model-class:
class Category < ActiveRecord::Base
has_many :articles,
:foreign_key => 'category'
belongs_to :parent_object,
:foreign_key => "parent",
:class_name => "Category"
has_many :children,
:foreign_key => "parent",
:class_name => "Category",
:order => "name ASC",
:dependent => :delete_all
#tree = Hash.new
#treepart = Hash.new
def self.category_tree
#root_categories = self.find(:all, :conditions => ["parent = ?", 0])
if #root_categories.length >= 1
#root_categories.each do |level|
#tree[level.id] = level.child_loop(level)
end
#tree
end
end
def child_loop(child)
#treepart = { :category => child }
#treepart[:children] = Hash.new
child.children.each do |child|
#treepart[:children][child.id] = child.child_loop(child)
end
#treepart
end
end
Categories can be nested therefore I have integrated a self-relating belongs_to and has_many function. I have called the parent *:parent_object* because only :parent does not work. Maybe it is in conflict with the attribute-name.
In the model I collect all categories with the method *category_tree* and *child_loop*. After this call I get an image of the category-tree in form of a hash.
Category.category_tree
I do this directly in the Articles´ *_form.html.erb* and pass it to my helper which is generating the html. Here is the call from the from-template:
<%= build_category_tree(Category.category_tree).html_safe %>
The helper is rendering as follows:
module CategoriesHelper
def build_category_tree(object_tree)
tree = object_tree
#treestring = "<ul>" + self.level_loop(tree) + "</ul>"
end
def level_loop(level)
#levelstring = ''
if !level.nil?
level.each do |id,item|
if item.has_key?(:category) && !item.nil?
#levelstring += "<li>" + item[:category].name + "</li>"
#levelstring += "<ul>" + self.level_loop(item[:children]) + "</ul>"
end
end
end
# in the end, return string to prevent a nil return
#levelstring += ""
end
end
Is this the ruby-way to code, can I shorten or change something completely?
Thanks for your help
active_scaffold :formats do |config|
format_order = Format.find(:all, :select => :format_order, :order => :format_order).collect(&:format_order)
format_order << format_order.size + 1 # I want only implement when new
config.columns = [:name, :format_order]
config.columns[:format_order].form_ui = :select
config.columns[:format_order].options = {
:options => format_order
}
config.list.columns = [:name, :format_order]
end
I want if I new format, format_order will add a number as code (format_order.size + 1) but I don't want implement it for edit. please help me. thanks
You will have to implement a callback:
class Format < ActiveRecord::Base
...
after_validation(:on => :create) do
self.code = whatever_code_you_want_to_assign #
end
...
end