Closed reconstructions closed 10 years ago
This is most probably because you use representers as modules. They in turn get extend
ed into a model and then stay there. This model, for whatever reasons, now slips into the capybara workflow and Tempfile
calls to_hash
- which is the "wrong" method from Roar.
That is just a guess, but that should be it.
Either find out how your object remains extended with a representer and why it ends up in the Capybara flow, or use Decorator
s instead of module representers.
A weird problem, I have to admit. Would be interesting to see how your representers are used in the app?!
Thanks for your helpful response. Sounds convincing about module vs. class representers. How representers are getting called from Capybara I can't tell. As far as I can tell I am only referencing the representers in controller specs, not request specs. Here is some of my code:
app/representers/songs_representer.rb:
module SongsRepresenter
include Representable::JSON::Collection
include Roar::Representer::JSON
self.representation_wrap = :songs
collection :songs do
property :id
property :song_name
end
end
app/controllers/songs_controller.rb
class SongsController < ApplicationController
include Roar::Rails::ControllerAdditions
respond_to :json, :html
def index
@search = Sunspot.search(Song) do
fulltext params[:search]
paginate(page: params[:page])
end
@songs = @search.results
respond_with @songs,
:represent_with => SongsRepresenter
end
end
app/spec/controllers/songs_controller_spec
require "spec_helper"
describe SongsController do
include Representable::JSON::Collection
before do
@valid_session = {}
@valid_attributes = attributes_for(:song)
@invalid_params = attributes_for(:song, song_name: nil)
end
before(:each) do
create_signed_in_user(no_capybara: true)
@song = create(:song, :song_album => "Metallica") # :song_name => "Sandman"
end
describe "GET #index" do
before(:each) do
@dead_flowers = create(:song,
:song_name => "Dead Flowers",
:song_album => "Sticky Fingers",
:song_author => "The Rolling Stones" )
end
context "html with no search term" do
before { get :index, {search: ""}, @valid_session }
it "performs a sunspot Song search" do
Sunspot.session.should be_a_search_for(Song)
Sunspot.session.should have_search_params(:fulltext, "")
end
# this correctly assigns @songs with two songs, :song_name => ["Sandman", "Dead Flowers"]
it "return all records as @songs" do
expect(assigns(:songs)).to match_array(Song.solr_search.results)
end
it "and render the #index template" do
expect(response).to render_template :index
end
end
context "in .json format" do
before do
@songs_expected = %({"songs":[{"song_name":"Dead Flowers"}, {"song_name":"Sandman"}]})
end
# Here is where I am extending with representers in tests
it "formats .json using a SongsRepresenter, testing using extend" do
# this is a passing test, but perhaps the one causing the failures?
songs_json = [@dead_flowers, @song].extend(SongsRepresenter).to_json
expect(songs_json).to be_json_eql(@songs_expected)
end
# And there is more trouble down here...
describe "with no search term" do
before do
# this is the same sunspot search which works fine in html above...
get :index, {search: ""}, :format => :json
# and in the browser it returns the same json string as @songs_expected:
#=> {"songs":[{"song_name":"Dead Flowers"}, {"song_name":"Sandman"}]}
end
# but even though these are passing
it "performs a sunspot Song search" do
Sunspot.session.should be_a_search_for(Song)
Sunspot.session.should have_search_params(:fulltext, "")
end
# this fails and says that the response.body does not eql @songs_expected
# see details of test failure below
it "responds with .json for an array of @songs" do
expect(response.body).to be_json_eql(@songs_expected)
end
end
end
end
end
This isn't too legible without code coloring, but basically the test gets null from response.body while the browser gets the expected json.
1) SongsController GET #index in .json format with no search term responds with .json for an array of @songs
Failure/Error: expect(response.body).to be_json_eql(@songs_expected)
Expected equivalent JSON
Diff:
@@ -1,11 +1,2 @@
-{
- "songs": [
- {
- "song_name": "Dead Flowers"
- },
- {
- "song_name": "Sandman"
- }
- ]
-}
+null
I tried swapping out my SongsRepresenter module for a SongsRepresenter class:
class SongsRepresenter < Representable::Decorator
include Representable::JSON
include Representable::JSON::Collection
include Roar::Representer::JSON
self.representation_wrap = :songs
collection :songs do
property :id
property :song_name
end
end
And using it in my tests like this:
it "formats .json using a SongsRepresenter, testing using class decorator" do
songs_json = SongsRepresenter.new([@dead_flowers, @song]).to_json
expect(songs_json).to be_json_eql(@songs_expected)
end
Which produced apparently inconsistent results in the browser:
#=> {"songs":[{"id":330,"song_name":"Dead Flowers","song_author":"The Rolling Stones","song_album":"Sticky Fingers","created_at":"2014-07-12T02:07:54.087Z","updated_at":"2014-07-12T02:07:54.087Z"}]}
And the shell, where only the attributes seemed to present, with all the brackets missing:
1) SongsController GET #index in .json format formats .json using a SongsRepresenter, testing using extend
Failure/Error: expect(songs_json).to be_json_eql(@songs_expected)
Expected equivalent JSON
Diff:
@@ -1,9 +1,13 @@
{
"songs": [
{
+ "song_album": "Sticky Fingers",
+ "song_author": "The Rolling Stones",
"song_name": "Dead Flowers"
},
{
+ "song_album": "Metallica",
+ "song_author": "Motorhead",
"song_name": "Sandman"
}
]
The undefined method 'collect' for nil:NilClass errors
(which precede the use of sunspot) are an annoyance, and I would like to be able to easily swap out classes for modules, but the problem with my sunspot tests I have to solve, and on that one I am stumped. I would really appreciate some insight on that failure!
Thanks in advance for the help.
Is using Decorator
an option for you at the moment?
By the way, you're using JSON::Collection
wrong. After including this module, you can't use ::collection
anymore, but have to use ::items
. The correct use is documented here.
I looked through your code, you should not include JSON::Collection
. This module is meant to represent a bare "lonely" collection, whereas in your representation you want a songs:
key. Why are you doing it so complicated :grin: - just do this.
class SongsRepresenter < Representable::Decorator
include Roar::Representer::JSON
collection :songs do
property :id
property :song_name
end
end
Does that do the trick??
BTW, using Decorator
is the way to go. I will discourage modules in Representable - they still work, but are slower and pollute your domain objects - hence our discussion here, hahaha.
Thanks for the tips - I guess the answer is that I was running into a lot of problems working with collections, which went away when I started including JSON::Collection. I found it difficult to identify a best practice which just worked in the documentation, so there was a lot of trial error pulling from Roar and Representable involved in getting things to work at all, thus the use of extend. I will start refactoring and see if I can clean things up. Thanks a lot for the pointers.
Trying to get this working with classes is reminding me of why I ended up using modules. I had a fairly simple use case, but modules was the only way I get what I needed using Roar/Representable.
I was trying to get a JSON API working which would produce this result from a singular Rails #show controller action:
{"songs":[{"id":11,"song_name":"Dead Flowers","song_author":"The Rolling Stones","song_album":"Sticky Fingers"}]}
And this one from a plural Rails #index controller action:
{"songs":[{"id":11,"song_name":"Dead Flowers","song_author":"The Rolling Stones","song_album":"Sticky Fingers"},{"id":12,"song_name":"The Wind Cries Mary","song_author":"Jimi Hendrix","song_album":"Are You Experienced?"}]}
I was able to do this with some difficulty, but it wasn't easy because of the magic which goes on with plural and singular Representer names. This worked (albeit with modules not classes):
class SongsController < ApplicationController
def index
@songs = ....
respond_with @songs,
:represent_with => SongsRepresenter
end
def show
@song = ....
respond_with @song,
:represent_with => SongRepresenter
end
If I didn't do it this way, I would end up with the usual kinds of errors you have when you treat collections as instances or vice versa, or when names are plural when they should be singular.
I got around this by having two representers:
app/representers/song_representer.rb
module SongRepresenter
include Roar::Representer::JSON
collection :songs do
property :id
property :song_name
property :song_author
property :song_album
end
def songs
[represented]
end
end
app/representers/songs_representer.rb
require 'representable/json/collection'
module SongsRepresenter
include Representable::JSON::Collection
include Roar::Representer::JSON
self.representation_wrap = :songs
collection :songs do
property :id
property :song_name
property :song_author
property :song_album
end
end
This did produce the correct strings, but I am sure it is a terrific hack and not how your framework was intended to be used. My problem was really that I couldn't figure out how to use the same Representer with either a collection or an instance variable, but however hard I looked, I couldn't find that in the documentation. I feel like I am going down a similar road with Cells.
I suspect one Representer should be able to do this, and if you could show me how, that could be added to the Wiki as a JSON API example. I have a lot of modules to tear out in my app, but if I could just understand the proper use I think I would be good to go.
Thanks again for the help - I tried to tear out some modules but immediately ran into this collections vs. instances problem in my #show action...
I can get close like this:
require 'roar/decorator'
class SongRepresenter < Roar::Decorator
include Roar::Representer::JSON
self.representation_wrap = :songs
property :id
property :song_name
property :song_author
property :song_album
def songs
[represented]
end
end
With this in my controller:
render :json => SongRepresenter.new(@song).to_json
But it still lacks the brackets that should be around the songs collection:
returned:
{"songs":{"id":11,"song_name":"Dead Flowers","song_author":"The Rolling Stones","song_album":"Sticky Fingers"}}
wished for:
{"songs":[{"id":11,"song_name":"Dead Flowers","song_author":"The Rolling Stones","song_album":"Sticky Fingers"}]}
I can't use represented
like I was before now that I am not working with a collection?
If I do this:
require 'roar/decorator'
class SongsRepresenter < Roar::Decorator
include Roar::Representer::JSON
self.representation_wrap = :songs
collection :songs do
property :id
property :song_name
property :song_author
property :song_album
end
end
And this in my controller:
render :json => SongsRepresenter.new(@song).to_json
I get an undefined method error:
undefined method 'songs' for #<Song:0x000001088ea2e8>
Thanks in advance!
If I use a class:
require 'roar/decorator'
class SongsRepresenter < Representable::Decorator
include Roar::Representer::JSON
collection :songs do
property :id
property :song_name
end
end
In my controller #index:
def index
@songs = Song.order(:song_name)
respond_with @songs do |format|
format.json { render :layout => false, :text => SongsRepresenter.new(@songs).to_json }
format.html
end
end
Then I get this error:
undefined method 'songs' for #<ActiveRecord::Relation::ActiveRecord_Relation_Song:0x00000108f890e0>
Whereas using a module in #index:
require 'representable/json/collection'
module SongsRepresenter
include Representable::JSON::Collection
include Roar::Representer::JSON
self.representation_wrap = :songs
collection :songs do
property :id
property :song_name
property :song_author
property :song_album
end
end
And this in my controller:
def index
@songs = Song.order(:song_name)
respond_with @songs,
:represent_with => SongsRepresenter
end
Everything works fine:
{"songs":[{"id":11,"song_name":"Dead Flowers","song_author":"The Rolling Stones","song_album":"Sticky Fingers"},{"id":12,"song_name":"The Wind Cries Mary","song_author":"Jimi Hendrix","song_album":"Are You Experienced?"},{"id":13,"song_name":"The Thrill is Gone","song_author":"B.B. King","song_album":"Completely Well"},{"id":14,"song_name":"Rock Me Baby","song_author":"B.B. King","song_album":"Rock Me Baby"},{"id":15,"song_name":"Pentatonic B*mb","song_author":"Recon Vancouver","song_album":"NA"}]}
You're lucky :grin: I started working with @oliverbarnes on JSON-API about a week ago.
Also, I want to make it easier to automatically have a collection representer for a single representer. My next week is allocated for that.
but however hard I looked, I couldn't find that in the documentation. I feel like I am going down a similar road with Cells.
I do understand your frustration about "the lack of documentation". I'm with you and you can always email me, hit me up on IRC or Skype, and so on. Nevertheless, at the same time, I have to state that every feature of Representable is documented in the README. Same goes for Cells. Naturally, this doesn't explain all your open questions. That's why I started writing a book about that.
Including JSON::Collection
into a representer that also defines collection
is wrong and please don't do it. It is very very simple: Defining a property
or collection
will always call that method on the represented object when rendering.
Including JSON::Collection
treats the represented object as an array (usually..... dunno how you managed to get that working) :wink: .
No worries ( or drama ), really appreciate the feedback and help. I am one of the ones who has been waiting for the book for sure ( A New Architecture for Rails?). I like both Cells and Roar/Representable.
I have removed the extend from my tests and things have been really stable ever since and some other bugs and failures have gone away. I am starting to refactor Representers as you suggested, perhaps just pulling out all the #show responses and moving the #index responses over to classes.
I think it would be great to try and demonstrate all the functionality in your docs using class decorators instead of module extension, so perhaps I will play with that some after I am done refactoring. Thanks again for the help and direction, and sorry to take time away from your book project! - Dave
Dave- your input is always very valuable to me and the projects, so please don't hesitate to email me when you're in trouble.
No worries, Nick, thanks for the help. Here is a little thing I wrote on using Paloma with jQuery UJS and link_to
tags:
I would love to try and do a step-by-step tutorial like this on some Cell use cases... Or perhaps we have that to look forward to when your book is out!
All the best,
On Jul 17, 2014, at 3:20 PM, Nick Sutterer notifications@github.com wrote:
Dave- your input is always very valuable to me and the projects, so please don't hesitate to email me when you're in trouble.
— Reply to this email directly or view it on GitHub.
What exactly is Paloma, @reconstructions ? Is that a JS library to support page updates from a Rails controller? Looking good!
I have persistent intermittent
undefined method 'collect' for nil:NilClass
failures in rSpec/Capybara request specs. Different tests fail, but it is usually the failure to find a button or the failure to reach a page with a button click which causes them. Their are some other weird failures as well, but this one is the only stack trace I have right now.I can point you to the app if it would help. Thanks!