I have a several models in a heirarchy, 1:many at each level. Each class is associated only with the class above it and the one below it, ie:
L1 course,
L2 unit,
L3 unit layout,
L4 layout fields,
L5 table fields (not in code, but a sibling of layout fields)
I am trying to build a JSON response of the entire hierarchy.
def show
#course = Course.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json do
#course = Course.find(params[:id])
#units = #course.units.all
#unit_layouts = UnitLayout.where(:unit_id => #units)
#layout_fields = LayoutField.where(:unit_layout_id => #unit_layouts)
response = {:course => #course, :units => #units, :unit_layouts => #unit_layouts, :layout_fields => #layout_fields}
respond_to do |format|
format.json {render :json => response }
end
end
end
end
The code is bring back the correct values, but the units, unit_layouts and layout_fields are all nested at the same level under course. I would like them to be nested inside their parent.
You need to use to_json with :include to include the associated records.
Here's a stab at it:
#course = Course.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json do
render :json => #course.to_json(:include => { :units => { :include => :layouts } })
end
end
It's probably not 100% correct, because you haven't included all the names of your associations, but I'm assuming that Unit has_many Layouts. To include the deeper nesting, add additional nested :includes.
Related
I have the shop with has_many association and include items to it so that the items belonging to that shop is received
format.json { render json: {:shop => #shops.as_json(:include => :items)}}
now it gives all the items that belongs to that shop but i want to get items with specific condition, say item_type = "accessories". so how can i do this? please help me.
EDIT
I have put a new question in How to get the value of include with conditions?
You should filter the data with ActiveRecord, and then call the as_json on the filtered data.
You can do something like:
#shops = Shop.includes(:items).where("items.itemp_type = ?", 'accesories')
format.json { render json: { :shop => #shops.as_json(:include => :items) } }
One way you could do this is to create a hash with the objects you want to render, and then pass that to the render method. Like so:
respond_to do |format|
format.json { render :json => {:shops => #shops,
:items => #items }}
end
If the models aren't associated through active record, that's probably your best solution.
If an association does exist, you can pass an :include argument to the render call, like so:
respond_to do |format|
format.json { render :json => #shops.to_json(:include => [:items])}
end
Note that you wouldn't have to retrieve the #items variable in the section above if you take this approach, Rails will automatically load it from the #shops variable.
I'm using Rails 3 for this one. I've got a collections model, a user model and an intermediate subscription model. This way a user can subscribe to multiple collections, with a particular role. However, I don't want a user to be able to subscribe to the same collection twice.
So in my Subscription model I've got something like:
validate :subscription_duplicates
def subscription_duplicates
self.errors.add_to_base "This user is already subscribed" if Subscription.where(:user_id => self.user.id, :collection_id => self.collection.id)
end
However this seems ugly. Also, it breaks when I want to do something like the following in my collection controller:
def create
#collection = Collection.new(params[:collection])
#collection.subscriptions.build(:user => current_user, :role => Subscription::ROLES['owner'])
#collection.save
respond_with(#collection)
end
When I do the build the subscription does not have an id so I get a "Called id for nil" error.
Thanks for any guidance!
use validates_uniqueness_of
validates_uniqueness_of :user_id, :scope => :collection_id
First of all, your create action should always test if the object was saved, and if not then handle that (usually by re-rendering the new/edit page and showing the errors to the user).
A standard sort of create action would look like this (for a #post in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to #post }
format.xml { render :xml => #post, :status => :created, :location => #post }
format.js
else
format.html { render :action => :new } #or edit or wherever you got here from
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
format.js
end
end
end
Shingara's approach to avoiding duplicates should work fine for you.
This is something I've been stuck on for a while now, and I have to apologize in advance for going into so much detail for such a simple problem. I just want to make it clear what I'm trying to do here.
Scenario
So, there's a model Foo, each Foo can either be red, green, or blue. Having URLs like /reds to list all red objects, and /reds/some-red-object to show a certain object. In that "show" view, there should be next/previous links, that would essentially "find the next RedFoo in alphabetical order, and once at the last RedFoo, the next record should be the first GreenFoo, continuing in alphabetical order, and so on".
I've tried implementing this in a couple of ways and mostly ended up at a roadblock somewhere. I did get it working for the most part with single table inheritance though, having something like this:
class Foo < ActiveRecord::Base
class RedFoo < Foo
class GreenFoo < Foo
class BlueFoo < Foo
Each subclass's models and controllers are identical, just replace the model names. So the controllers look something like:
class RedFoosController < ApplicationController
def index
#foos = RedFoo.find(:all, :order => "title ASC")
respond_to do |format|
format.html { render :template => 'foos/index'}
format.xml { render :xml => #foos }
end
end
def show
#foo = RedFoo.find(params[:id])
respond_to do |format|
format.html { render :template => 'foos/show'}
format.xml { render :xml => #foo }
end
end
def new
#foo = RedFoo.new
respond_to do |format|
format.html { render :template => 'foos/new'}
format.xml { render :xml => #foo }
end
end
def edit
#foo = RedFoo.find(params[:id])
respond_to do |format|
format.html { render :template => 'foos/edit'}
end
end
def create
#foo = RedFoo.new(params[:foo])
respond_to do |format|
if #foo.save
flash[:notice] = 'Foo was successfully created.'
format.html { redirect_to(#foo) }
format.xml { render :xml => #foo, :status => :created, :location => #foo }
else
format.html { render :action => "new" }
format.xml { render :xml => #foo.errors, :status => :unprocessable_entity }
end
end
end
def update
#foo = RedFoo.find(params[:id])
respond_to do |format|
if #foo.update_attributes(params[:foo])
flash[:notice] = 'Foo was successfully updated.'
format.html { redirect_to(#foo) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #foo.errors, :status => :unprocessable_entity }
end
end
end
def destroy
#foo = RedFoo.find(params[:id])
#foo.destroy
respond_to do |format|
format.html { redirect_to(foos_url) }
format.xml { head :ok }
end
end
end
The models only contain methods for next/previous, which work fine, surprisingly.
class RedFoo < Foo
def next
if self == RedFoo.find(:all, :order => "title ASC").last
GreenFoo.find(:all, :order => "title ASC").first
else
RedFoo.find(:first, :conditions => ["title > ?", self.title], :order => "title ASC")
end
end
def previous
if self == RedFoo.find(:all, :order => "title ASC").first
BlueFoo.find(:all, :order => "title ASC").last
else
RedFoo.find(:first, :conditions => ["title < ?", self.title], :order => "title DESC")
end
end
end
Problem
For whatever reason when I try to create and edit records, none of the attributes get saved in the database. It simply adds a new record with completely empty columns, regardless of what's filled in the form. No errors get returned in the script/server output or in the log files. From the script/console however, everything works perfectly fine. I can create new records and update their attributes no problem.
It's also quite a bad code smell that I have a lot of code duplication in my controllers/models (they're using the same views as the base model, so that's fine though). But I think that's unavoidable here unless I use some meta-goodness.
Any advice or suggestions about tackling this record saving issue would be great, but the reason I posted my setup in detail is because I have a feeling I'm probably going about this whole thing the wrong way. So, I'm open to other approaches if you know of something more practical than using STI. Thanks.
Update
The parameters hash looks about right:
{"commit"=>"Create", "authenticity_token"=>"+aOA6bBSrZP2B6jsDMnKTU+DIAIkhc8fqoSicVxRJls=", "red_foo"=>{"title"=>"Hello world!"}}
But #foo.inspect returns the following RedFoo object (all nil, except for type):
#<RedFoo id: nil, title: nil, type: "RedFoo", created_at: nil, updated_at: nil>
Problem is the params
:red_foo
is the name of the params in the view, whereas you use
params[:foo]
in the controller, I think the best way would be to be use :foo, in the view by using text_field_tag rather than any (what i assume can be) form builders text_field.
You can get out of the controller smell by using a module to do the basic crud stuff, since i assume most of the new/create/edit/update/destroy stuff is the same
OR
you could map all the routes to a foo controller and use some sort of parameter either passed in from the route, or through URI analysis to get the red/green/blue foo
Please take a look at the section called "Single table inheritance" on this page and let us know if it solves your problem.
Must admit, the way I go about STI is to use set_table_name inside a model.
e.g.
class RedFoo < AR::Base
set_table_name "foos"
include FooModule
extend FooClassModule # for self methods
def next; ...; end
end
But anyway, for this situation, what does your logger say when you do a #foo.inspect just before a save, and also what is the SQL that is ran on insert/update?
Right, so #foo.inspect gives you "nil" in the log?
What I mean (if I wasn't clear enough) was:
def create
#foo = RedFoo.new(params[:foo])
logger.error "******************* foo: #{#foo.inspect} **************"
respond_to do |format|
if #foo.save
...
if you do that and tail -f your log you can easily find out what is happening to foo and compare that to the incoming params hash
Infact, that would also be some useful information to have, what is the params hash?
I'd love to use render :json but it seems its not as flexible. Whats the right way to do this?
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #things }
#This is great
format.json { render :text => #things.to_json(:include => :photos) }
#This doesn't include photos
format.json { render :json => #things, :include => :photos }
end
I've done something similar with render :json. This is what worked for me:
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #things.to_json(:include => { :photos => { :only => [:id, :url] } }) }
end
I guess this article can be useful for you - Rails to_json or as_json? by Jonathan Julian.
The main thought is that you should avoid using to_json in controllers. It is much more flexible to define as_json method in your model.
For instance:
In your Thing model
def as_json(options={})
super(:include => :photos)
end
And then you can write in your controller just
render :json => #things
Managing complex hashes in your controllers gets ugly fast.
With Rails 3, you can use ActiveModel::Serializer. See http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
If you're doing anything non-trivial, see
https://github.com/rails-api/active_model_serializers. I recommend creating separate serializer classes to avoid cluttering your models and make tests easier.
class ThingSerializer < ActiveModel::Serializer
has_many :photos
attributes :name, :whatever
end
# ThingsController
def index
render :json => #things
end
# test it out
thing = Thing.new :name => "bob"
ThingSerializer.new(thing, nil).to_json
format.json { render #things.to_json(:include => :photos) }
in case of array what I done is
respond_to do |format|
format.html
format.json {render :json => {:medias => #medias.to_json, :total => 13000, :time => 0.0001 }}
end
I have an ActiveRecord model that I would like to convert to xml, but I do not want all the properties rendered in xml. Is there a parameter I can pass into the render method to keep a property from being rendered in xml?
Below is an example of what I am talking about.
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :xml => #person }
end
end
produces the following xml
<person>
<name>Paul</name>
<age>25</age>
<phone>555.555.5555</phone>
</person>
However, I do not want the phone property to be shown. Is there some parameter in the render method that excludes properties from being rendered in xml? Kind of like the following example
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :xml => #person, :exclude_attribute => :phone }
end
end
which would render the following xml
<person>
<name>Paul</name>
<age>25</age>
</person>
You can pass an array of model attribute names to the :only and :except options, so for your example it would be:
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :text => #person.to_xml, :except => [:phone] }
end
end
to_xml documentation
I just was wondering this same thing, I made the change at the model level so I wouldn't have to do it in the controller, just another option if you are interested.
model
class Person < ActiveRecord::Base
def to_xml
super(:except => [:phone])
end
def to_json
super(:except => [:phone])
end
end
controller
class PeopleController < ApplicationController
# GET /people
# GET /people.xml
def index
#people = Person.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #people }
format.json { render :json => #people }
end
end
end
I set one of them up for json and xml on every object, kinda convenient when I want to filter things out of every alternative formatted response. The cool thing about this method is that even when you get a collection back, it will call this method and return the filtered results.
The "render :xml" did not work, but the to_xml did work. Below is an example
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :text => #person.to_xml(:except => [:phone]) }
end
end
The except is good, but you have to remember to put it everywhere. If you're putting this in a controller, every method needs to have an except clause. I overwrite the serializable_hash method in my models to exclude what I don't want to show up. This has the benefits of not having t put it every place you're going to return as well as also applying to JSON responses.