Accessing the child instance in a RABL template - ruby-on-rails

I have a RABL template as shown below
object #user
attributes :name
child :contacts do
# does not work
if contact.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end
How do I access the Contact object in the child block of the template? I need to perform some conditional logic on the child instance.

You can access the current object by declaring the block parameter.
object #user
attributes :name
child :contacts do |contact|
if contact.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end
Old answer
I ended up using the root_object method, which returns the data object in a given context.
object #user
attributes :name
child :contacts do
if root_object.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end

Another approach to keep things DRY:
contacts/show.json.rabl
object #contact
node do |contact|
if contact.is_foo?
{:a1 => contact.a1, :a2 => contact.a2}
else
{:a3 => contact.a3, :a4 => contact.a4}
end
end
users/show.json.rabl
object #user
attributes :name
child :contacts do
extends 'contacts/show'
end

Here's one way:
child :contacts do
node(:a1, :if => lambda { |c| c.is_foo? }
node(:a2, :if => lambda { |c| c.is_foo? }
node(:a3, :unless => lambda { |c| c.is_foo? }
node(:a4, :unless => lambda { |c| c.is_foo? }
end
Not exactly the same but one possibility, another is:
node :contacts do |u|
u.contacts.map do |c|
if contact.is_foo?
partial("contacta", :object => c)
# or { :a1 => "foo", :a2 => "bar" }
else
partial("contactb", :object => c)
# or { :a3 => "foo", :a4 => "bar" }
end
end
end

I know it's a late reply but came across the similar problem so thought of answering.
It's more like a hack but works.
When two variables are used as block argument contact and a random variable x, contact refers to an object of the collection
When one variable is used in block argument, it renders collection object
object #user
attributes :name
child :contacts do |contact, x|
if contact.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end

Related

Include nested json in Rails

I added an object nested in another object using the model. Just like this:
Ingresso model ->
def as_json(options=nil)
super(:include => [:usuario, :tipo_de_ingresso])
end
In tipo_de_ingresso model, I want to add another object nested. here:
def as_json(options=nil)
super(:include => :entradas)
end
But when I get the the ingressos.json, I lost entradas. If I get tipo_de_ingressos.json, entradas are nested, ok, but when I get ingressos.json, they are not there.
How can I get entradas nested in tipo_de_ingresso when I call ingresso?
Try this,
# /app/models/Ingresso.rb
def as_json(options=nil)
super(:include => [:usuario => {}, :tipo_de_ingresso => { :include => :entradas }])
end
EDIT:
changed [:usuario, ... to [:usuario => {}, ...

Destroying after save if association is blank?

How would I destroy a Product if it doesn't belong to a Store after_save?
class Product < ActiveRecord::Base
attr_accessible :price, :name, :product_store
belongs_to :store
attr_reader :product_store
# I need this to do also "p.product_store.blank?"
after_save { |p| p.destroy if p.name.blank? && p.price.blank? }
def product_store=(id) # using Jquery TokenInput so only needed this.
self.store_id = id
end
end
I've tried a couple of different approaches like:
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.product_store.blank? }
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.store.id.blank? }
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.store_id.blank? }
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.store.blank? }
But these didn't work so I ask for your help on how it would be done?
Here is my form and controller: https://gist.github.com/1472629
how about use ActiveModel::Validations?
you don't want to save record if some attribute not filled.so you need validation.
class Product < ActiveRecord::Base
validates_presence_of :name, :price, :store_id
end
Edit:
your code looks want to create muti products use common attributes.
may be params like this:
{"products" => {:1 => {:name => "good", :desc => "sss"}}, {:2 => {:name => "good", :desc => "tt"}}, "product" => {:price => "10"} }
controller
def create_multiple
#products = params[:products].values.collect do |up|
Product.new(up.merge(params[:product]))
end
if #products.each(&:save)
redirect_to :back, :notice => "Success!"
else
render :new
end
end
Validation do not need change. but the form will a bit complicate.
form_tag "/products" do
%p
= text_field_tag "product[price]", #products.first.price
= #products.first.errors[:price]
- #products.each_with_index do |product, idx|
= text_field_tag "products[#{idx}][name]", product.name
= product.errors[:name]
= submit_tag :submit
the code is simple, you can enhance the view yourself.
This seems more like a validation issue. If it's invalid without a Store, then you shouldn't save it to the DB without a Store reference at all.
If you want to destroy the Product you should do that directly, rather than saving a nil reference to a store, trying to catch that, and then destroying it as a result.
By trying to destroy a record when its association is incorrect, you're essentially saying, "This in an invalid state, and shouldn't be in the database in the first place."

How to update a model's attribute with a virtual attribute?

I have a model named UserPrice which has the attribute :purchase_date(a date_select) in its table. With my form I can create multiple user_prices at once but for user convenience I made a virtual attribute inside of my UserPrice model called :all_dates that's also a date_select field and its job is to be the replacement of the :purchase_dates so users only have to select the :all_dates field for the date.
Problem & Question
The :all_dates field is not updating the :purchase_date fields of my user_prices that are being created. What do I need to do in order to get my :all_dates field to update the :purchase_date fields of my new UserPrices?
Does anyone have any tips on how to do this?
Parameters
Parameters:
"user_price"=> {
"all_dates(2i)"=>"10",
"all_dates(3i)"=>"27",
"all_dates(1i)"=>"2011"
},
"user_prices"=>
{
"0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"},
"1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
},
"commit"=>"Submit"}
Code
class CreateUserPrices < ActiveRecord::Migration
def self.up
create_table :user_prices do |t|
t.decimal :price
t.integer :product_id
t.date :purchase_date
t.timestamps
end
end
end
I took out the :purchase_date field so it isn't inside of the user_price loop.
<%= form_tag create_multiple_user_prices_path, :method => :post do %>
<%= date_select("user_price", "all_dates" ) %>
<% #user_prices.each_with_index do |user_price, index| %>
<%= fields_for "user_prices[#{index}]", user_price do |up| %>
<%= render "user_price_fields", :f => up %>
<% end %>
<% end %>
<% end %>
class UserPrice < ActiveRecord::Base
attr_accessible :price, :product_name, :purchase_date, :all_dates, :store
attr_accessor :all_dates
after_save :save_all_dates_to_user_prices
composed_of :all_dates, :class_name => "DateTime",
:mapping => %w(Time to_s),
:constructor => Proc.new { |item| item },
:converter => Proc.new { |item| item }
def user_prices
#user_prices = Array.new() { UserPrice.new }
end
protected
def save_all_dates_to_user_prices
if !self.all_dates.nil?
self.user_prices.each {|up| up.purchase_date = self.all_dates if up.new_record?}
end
end
class UserPricesController < ApplicationController
def new
#user_prices = Array.new(5) { UserPrice.new }
end
def create_multiple
#user_prices = params[:user_prices].values.collect { |up| UserPrice.new(up) }
if #user_prices.all?(&:valid?)
#user_prices.each(&:save!)
redirect_to :back, :notice => "Successfully added prices."
else
redirect_to :back, :notice => "Error, please try again."
end
end
This is a case of trying to do in a model what is better left to the controller. All you're trying to do here is to auto-assign a certain attribute on creation from a parameter not directly tied to your model. But you're not even passing that extra parameter to the model anywhere - you're creating your model instances from the user_prices parts of the parameter hash, but the user_price sub-hash is not used anywhere. In any case, this is behavior that is more closely related to the view and action taken than the model, so keep it in the controller.
Try this:
Throw out the virtual attribute, and get rid of the whole after_save callback stuff
Throw away the user_prices method in your model
Change the all_dates attribute name back to purchase_date in the form
Then your parameter hash should look like this:
{"user_price"=> {
"purchase_date(2i)"=>"10",
"purchase_date(3i)"=>"27",
"purchase_date(1i)"=>"2011"
},
"user_prices"=>
{
"0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"},
"1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
}}
All that's left to do is to merge the single user_price attributeS into each user_prices sub-hash in your create_multiple action. Replace the first line in that action with this:
#user_prices = params[:user_prices].values.collect do |attributes|
UserPrice.new(attributes.merge(params[:user_price]))
end
I'm not sure why you are even using that virtual attribute is there more to this implementation? If you are just trying to save an associated model, you might simply want a accepts_nested_attributes_for :user_prices in your User model
This works great and many developers use this method, so it's nice to know for working on other projects as well as for the people who might end up maintaining yours.
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2

problem using 'as_json' in my model and 'render :json' => in my controller (rails)

I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.

How can I get form_for to autopopulate fields based upon a non-model hash?

I'm building a multi-step form in rails. It's not javascript driven, so each page has its own controller action like "step1" "step2" etc. I know how to do multi-step wizards through JQuery but I don't know how to keep rails validations per page without getting into javascript, hence this way.
Anyways, my model is a User object but I'm storing all my variables in an arbitrary Newuser variable and using the following in the view:
<% form_for :newuser, :url => { :action => "step3" } do |u| %>
In the controller, I merge the current page's info with the overall hash using:
session[:newuser].merge!(params[:newuser])
This works great except that if the user clicks back to a previous page, the fields are no longer populated. How do I keep them populated? Do I need to change the object in the form_for to somehow refer to the session[:newuser] hash?
EDIT:
I guess I'm looking for more info on how form_for autopopulates fields within the form. If it's not built around a model but an arbitrary hash (in this case, session[:newuser]), how do I get it to autopopulate?
This is how we did a multi-step form with validations
class User < ActiveRecord::Base
attr_writer :setup_step
with options :if => :is_step_one? do |o|
o.validates_presence_of :name
end
with options :if => :is_step_two? do |o|
o.validates_presence_of :email
end
def setup_step
#setup_step || 1
end
def is_step_one?
setup_step == 1
end
def is_step_two?
setup_step == 2
end
def last_step?
is_step_two? #change this to what your last step is
end
end
Then in the controller:
UsersController
SETUP_STEPS{1 => 'new', 2 => 'step_two'}
def new
#user = User.new
end
def step_two
end
def create
#user = User.new(params[:user])
if !#user.valid?
render :action => SETUP_STEPS[#user.setup_step]
elsif #user.last_step?
#user.save
#do stuff
else
render :action => SETUP_STEPS[#user.setup_step]
end
end
end
And then in your forms, they are like like any other rails form with one exception, you will need a hidden field to hold the values from your previous steps.
- form_for #user, :url => users_path do |f|
- [:login, :password].each do field
= f.hidden_field field
What about still using a class for your population?
class User
attr_accessor :credit_card, :name, :likes_fried_chicken
def initialize(options = {})
options.each do |key, value|
self.send("#{key}=", value)
end
end
end
you could use some tableless model functions here if you wanted to include some validations.
Then in your controller steps:
def step_one
#user = User.new(session[:new_user])
end
your forms should continue to work.
Another option is just to set the value of the form objects directly from your session hash
- form_for :user, :url => step_2_path do |f|
= f.text_field :name, :value => session[:new_user][:name]

Resources