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
Related
I have a Rails App with a Post Model and Controller and I'm using ActiveAdmin for CMS. I have implemented ElasticSearch and SearchKick and am now attempting to deploy to Heroku using SearchBox.
The app runs on local no issues and the majority of the functionality works on Heroku but I am getting a very annoying error when I Update or Create Post in Active Admin.
Elasticsearch::Transport::Transport::Errors::NotFound in
Admin::PostsController#update [404]
{"error":{"root_cause":[{"type":"document_missing_exception","reason":"[post][11]:
document
missing","index":"posts","shard":"0"}],"type":"document_missing_exception","reason":"[post][11]:
document missing","index":"posts","shard":"0"},"status":404}
Even though it is throwing this error the Post is still either Updating or Creating fine. If I refresh the page it resolves to the view all posts screen in ActiveAdmin as expected. The search functionality is fully operational on the front end.
In SearchBox the ElasticSearch index is named posts_production_20160220081603930. Can't work out how to make ActiveAdmin see it. The Post Model sees it and I am able to search as expected.
Post Controller...
class PostsController < ApplicationController
before_filter :find_post, :only => [:show, :edit, :update, :destroy]
def index
#post = Post.all
if params[:q].present?
#postsearch = Post.search params[:q], fields: [:title, :body], operator: "or", suggest: true
end
end
def show
if params[:q].present?
#postsearch = Post.search params[:q], fields: [:title, :body], operator: "or", suggest: true
else
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
end
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.json { render :json => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.json { render :json => #post.errors, :status => :unprocessable_entity }
end
end
end
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
format.html { redirect_to(#post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #post.errors,
:status => :unprocessable_entity }
end
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
end
end
private
def find_post
#post = Post.find(params[:id])
end
end
Post Model
require 'elasticsearch/model'
class Post < ActiveRecord::Base
searchkick suggest: [:title]
#add attachement declaration to moidels for refile image uploading
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
validates_presence_of :title, :body
attachment :profile_image
attachment :image
end
ActiveAdmin Post
ActiveAdmin.register Post do
# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
permit_params :title, :body, :profile_image, :image
form do |f|
inputs 'Details' do
input :title
input :body, :input_html => {:class => "redactor"}
input :profile_image, :required => false, :as => :file, destroy: false, :direct => true
input :image, :required => false, :as => :file, destroy: false, :direct => true
actions
end
end
end
To create the original index I ran
heroku run rake searchkick:reindex CLASS=Post
In dev the index gets updated automatically but in prod I have to reindex manually. Haven't setup sidekiq yet.
Any help would be greatly appreciated.
Am going a bit mad.
Cheers
Dan
Solved issue...
I had used Searchkick to create and index however quirkily it would appear that an index also needs to be created using Post.elasticsearch.create_index! force: true ie the non Searchkick command.
So the indices in Searchbox are
posts
and
posts_production
All fully operational.
I've never used Unit Test before but only Rspec. So maybe here's some silly mistake.
I have CountriesController:
class CountriesController < ApplicationController
def create
#country = Country.new(params[:country])
respond_to do |format|
if #country.save
format.html { redirect_to(#country, :notice => 'Country was successfully created.') }
format.xml { render :xml => #country, :status => :created, :location => #country }
else
format.html { render :action => "new" }
format.xml { render :xml => #country.errors, :status => :unprocessable_entity }
end
end
end
end
and test countries_controller_test.rb for it :
class CountriesControllerTest < ActionController::TestCase
should_not_respond_to_actions :new => :get, :destroy => :get
setup do
#country = countries(:one)
end
test "should create country" do
assert_difference('Country.count') do
post :create, :country => #country.attributes.merge({ :code => Time.now.to_s })
end
assert_redirected_to country_path(assigns(:country))
end
...
end
As far as I know the name convention, everything looks ok for me but I got an error:
1) Error:
test_should_create_country(CountriesControllerTest):
RuntimeError: #controller is nil: make sure you set it in your test's setup method.
What might be a problem here? Thanks
Where is the test's #controller supposed to come from ? If it's created by ActionController::TestCase, then you might want to call the superclass's setup in your own setup function ?
respond_to do |format|
if #user.save
format.js { render :nothing => true, :status => :ok, :location => #user }
else
format.js { render :json => #user.errors, :status => :unprocessable_entity }
end
end
All options I've tried (like putting respond_to :js at the top of controller, etc) don't quite work the way as in this.
Rails 3 Format:
Use respond_to :json and respond_with(#user)
respond_to :json # You can also add , :html, :xml etc.
def create
#user= User.new(params[:user])
#---For html flash
#if #user.save
# flash[:notice] = "Successfully created user."
#end
respond_with(#user)
end
# Also, add :remote => :true, :format => :json to the form.
Try using format.json instead of format.js in your controller and :remote => :true, :format => :json in corresponding form.
Though, I'm not quite sure whether format.json or format.js should be used in that case. As default scaffolding from Rails 3 generates controllers with format.json and you're doing render :json in response I believe format.json is the right way to go. And format.js should be used when you return a piece of JS that should be executed.
The URL your are requesting should end with .json like this: /controller/action.json
If that is not possible:
You should set the 'accepts' parameter to 'application/json' while sending the ajax request.
Search for how to use 'accepts' here: http://api.jquery.com/jQuery.ajax/
And in the server side:
format.json { render :json => #user.errors, :status => :unprocessable_entity }
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 have an action in my PostsController named 'tagged', which I want to return all posts tagged with whatever term.
In my routes.rb I have the following (at the top):
map.connect 'posts/tagged/:tag', { :controller => 'posts', :action => 'tagged', :tag => /[a-z\-]+/ }
Yet navigating to posts/tagged/yes returns a RecordNotFound error:
Couldn't find Post without an ID
In my tagged.html.erb file, I'll eventually be using the find_tagged_with method from acts_as_taggable_on_steroids, but for now I've put a simple Post.find(:all) to eliminate the possibility of error.
It seems like my map.connect is being overridden, and the same error occurs even if I comment the whole of the routes.rb file out except my new line.
Ok, because you can comment out the default routes that means your problem is not in your routes at all. It's that your tagged action in the posts controller probably has something like this.
def tagged
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
Or perhaps if you spent a little more time it looks like this:
def tagged
#post = Post.find(params[:tagged])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
Where as what you want is this:
def tagged
#post = Post.find(:all, :conditions => {:tagged => params[:tagged]})
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
Anyway, you should be writing functional tests for this stuff and not testing in the browser.
Why not add a RESTful route for the "tagged" action?
map.resources :posts, :member => { :tagged => :put }