Eager-loading in Rails 3.1 controller action which responds_to json - jqgrid

Here's what I have so far:
class Show < ActiveRecord::Base
belongs_to :event
# use default_scope so shows are ordered by date by default
default_scope order("date ASC")
end
class Event < ActiveRecord::Base
has_many :shows, :dependent => :destroy
has_and_belongs_to_many :users
scope :future, lambda { includes(:shows).joins(:shows).where("shows.date > ?", Date.today).group("event_id") }
def start_date
shows.first.date
end
def end_date
shows.last.date
end
def ends_in_future?
end_date > Date.today
end
end
I would like to create a controller action to use with jqGrid. So far using Event.includes(:shows).all.to_a returns all the shows in the JSON string, but I can't get hold of start_date and end_date, which is kinda understandable. Is it possible to have derived/calculated properties rendered in JSON?
I also notice the shows for each event are not in the JSON string. Is there any way I can get all the events, complete with child shows entities, rendered in the JSON string?
Many thanks,
Dany.
EDIT: Partially solved this by using as_json(:include => :shows) in the controller action. This returns the event and all the associated shows for each event. The only thing remaining is to figure out how I can include start_date and end_date in the json string...
EDIT: Here was my original controller action code - it may not be the best code since I'm still feeling my way around Rails:
matches = Event.includes(:shows).all
respond_to do |format|
format.json {render :json => matches.as_json(:include => :shows)}
end
As it turned out I don't have to run the query first - it can just be part of responding to the json request. I should've read the as_json specs a lot closer first! Here's the solution in my controller action:
respond_to do |format|
format.json {render :json => Event.all.as_json(:include => :shows, :methods => [:start_date, :end_date])}
end
That renders everything, including the "derived" method. Unfortunately that seems to generate a lot of queries. This provides the json string I want, but is it the best way? Would love to hear any improved methods.

You can get those columns selected into the Event record like so:
Event.includes(:shows).joins(:shows).select("events.*, MIN(shows.date) as start_date, MAX(shows.date) as end_date").all
You may need to alias those columns by a different name (e.g. 'show_start_date', 'show_end_date') because the Event records will already have start_date and end_date methods that you've defined. You'll have to work around that method name collision.

Related

Getting Sequel associations through Sinatra

I'm trying to return json-formatted data from my Sinatra REST API. I currently have a bunch of associations set up, but I'm having trouble getting the views I want from my API despite getting them easily in Ruby.
For example, from my tables:
DB.create_table?(:calendars) do
primary_key :id
end
DB.create_table?(:schedules) do
primary_key :id
foreign_key :resource_id, :resources
foreign_key :task_id, :tasks
foreign_key :calendar_id, :calendars
end
In Ruby, I'm able to run a block like this and display all the info I need through my associations:
Calendar.each do |c|
c.schedules.each do |s|
puts "RESOURCE ##{s.resource_id}"
s.tasks.each do |t|
p t
end
puts
end
end
the c.schedules call works because my calendar model contains a one_to_many :schedules association.
Now, I'm wondering how this translates to my Sinatra API. In my simple GET route, I've tried many variations trying to get the schedules associated with a calendar, and convert it to JSON:
get '/calendars' do
c = DB[:calendar].first
c.schedules.to_json
content_type :json
end
... but I'll end up with an error like undefined method 'schedules' for {:id=>1}:Hash
So it looks like it's returning a hash here, but I've tried a bunch of stuff and haven't figured out how I'm supposed to work with my associations in Sinatra. How can I do this?
Thanks!
The reason your first block works but the second doesn't is because in the first case, you're using a Sequel model instance of class Calendar, whereas in the second case you're using a Sequel dataset.
When you iterate over Calendar.each do |c|, the c variable gets populated with an instance of a Calendar class Sequel model object. This object has relationship methods defined (one_to_many) and you're able to query schedules and run other model methods on it.
However, c = DB[:calendar].first gets you a Sequel dataset. This object is different than a model instance, it returns a standard Ruby hash (or an array of hashes).
You can change your 2nd block to use a model instead and it will get the result you want:
get '/calendars' do
c = Calendar.first # <=== CHANGE FROM DATASET TO MODEL
c.schedules.to_json
content_type :json
end

RSpec Testing Controller with Model.create

I'm trying to test a controller to ensure that only an authorized party can view the correct child object using RSpec. I cant figure out what I'm doing wrong as I'm getting this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I have a Plan object and a Company object. The Store can have many plans (think of a pest control Company). I want to test that given a known scenario I can retrieve the plan fo the Company (assuming there is only one).
The Plan looks like this:
class Plan < ActiveRecord::Base
before_save :default_values
# Validation
validates :amount, :presence => true
validates :company, :presence => true
# Plans belong to a particular company.
belongs_to :company, :autosave => true
scope :find_all_plans_for_company, lambda {
|company| where(:company_id => company.id)
}
# Other code ...
end
The Company looks like this:
class Company < ActiveRecord::Base
validates :name, :presence => true
validates :phone1, :presence => true
validates_format_of :phone1, :phone2,
:with => /^[\(\)0-9\- \+\.]{10,20}$/,
:message => "Invalid phone number, must be 10 digits. e.g. - 415-555-1212",
:allow_blank => true,
:allow_nil => true
has_many :users
has_many :plans
end
.. controller looks like this
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #plans }
end
end
.. and my RSpec test looks like this (excuse me if its full of gimmickery, I'm just splunking around with it and cannot get it to work).
describe PlansController do
def valid_attributes
{
:company_id => 1,
:amount => 1000
}
end
describe "GET index" do
it "should return the Plans for which this users company has" do
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
Company.stub(:find).with(#company.id).and_return(#company)
controller.stub_chain(:current_user, :company).and_return(#company)
plan = Plan.create! valid_attributes
get :index, {}
assigns(:plans).should eq([plan])
end
# Other tests ...
end
end
The problem is, when I try this (or any of the crazy other variants I've tried) I get this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I'm not sure why this is happening as I thought the Company.stub call would handle this for me. But apparently not.
What am I missing here and what am I doing wrong? How can I get this test to pass?
Let's peel back the layers on this spec, to make sure things make sense (and to make sure I understand what's going on). First, what are you testing?
it "should return the Plans for which this users company has" do
...
assigns(:plans).should eq([plan])
So you want to check that the plans associated with the company of the current user are assigned to #plans. We can stub or mock out everything else.
Looking at the controller code, we have:
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
What do we need to get this to work, without hitting the database and without depending on the models?
First of all, we want to get a mock company out of current_user.company. This is what these two lines in your spec code do:
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
controller.stub_chain(:current_user, :company).and_return(#company)
This will cause current_user.company to return the mock model #company. So far so good.
Now to the class method find_all_plans_for_company. This is where I'm a bit confused. In your spec, you stub the find method on Company to return #company for id = 1.
But really, wouldn't it suffice just to do something like this in your controller code?:
#plans = current_user.company.plans
If you did it this way, then in your test you could just mock a plan, and then return it as the plans association for your mock company:
#plan = mock_model(Plan)
#company = mock_model(Company, :plans => [ #plan ])
controller.stub_chain(:current_user, :company).and_return(#company)
Then the assignment should work, and you don't need to actually create any model or hit the database. You don't even need to give your mock company an id or any other attributes, which anyway are irrelevant to the spec.
Maybe I'm missing something here, if so please let me know.
Why do you need to mock?
My standard testing setup is to use Database Cleaner which clears out the database from any records created during tests. In this way, the tests are run with real database records which are consequently deleted from the test database after each test.
You might also like taking a look at Factory Girl for creating instances of your models during testing (makes it easy to create 10 company records, for example).
See:
http://rubygems.org/gems/database_cleaner
http://rubygems.org/gems/factory_girl
I have three thoughts coming up that could resolve your issue:
Try adding attr_accessible :company_id to Plan class.
Because mock_model does not actually save to the database when you create a Plan with company_id of 1 it fails validation since it is not present in the database.
Ensure before_save :default_values in Plan class does not mess with company_id attribute of the newly created instance.

Rails 3 nested attributes - association as choice

I am building a questionnaire for my website and I have models set up like so:
Question - has_many :answers
Answer - belongs_to :question
Response - belongs_to :answer, :user_application
UserApplication - has_many :responses
Answers are set answers, they could be Yes, No or a sentence, Responses are a nested resource of UserApplication so when I am hitting the new action in the UserApplicationsController I build responses, based on the number of questions, so I can use f.fields_for in the new form. When it renders I want the nested response to have radio buttons which have the id for the answer. So when the form is submitted the nested response receives the id for the selected answer. I just can not figure out the best way to do this. At the moment I am doing it like this:
UserApplicationController
def new
#user_application = UserApplication.new
#questions = Question.all
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user_application }
end
end
New Form
= form_for #user_application do |f|
- #questions.each_with_index do |question, idx|
= question.content
.control-group
.controls
= select_tag "user_application[responses_attributes][#{idx}][answer_id]", options_for_select(question.answers.map {|u| [u.content,u.id]})
This feels a little hacky because when the user edits the application the select boxes for the previous answers are not selected. I have tried to explain my problem as best as possible. If anyone can understand what I am trying to do then any help would be appreciated! If you need to know anything ask!
Thanks
Tony

What would I need in a Rails 3 controller to process incoming emails and insert them into the database?

I'm trying to set up my RoR 3 application to receive emails and then process those emails and insert them into the database.
In my application, I have a "jobs" controller. Users can create jobs. I also have a "comments" controller. Users can create comments on jobs.
Here is (part of) what I have in my comments controller:
def new
#job = Job.find(params[:job_id])
#comment = #job.comments.build
end
def create
#job = Job.find(params[:job_id])
#comment = #job.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.commenter = current_user.login
if #comment.save
redirect_to #job
else
render :action => "new"
end
end
When users add a comment, the admin receives an email.
When admins add a comment, users receive an email.
(This is already functioning.)
I'm using Cloudmailin to help me receive incoming mail. I've set up the Cloudmailin address to point to http://myapp.com/incoming_mails.
class IncomingMailsController < ApplicationController
require 'mail'
skip_before_filter :verify_authenticity_token
def create
another_comment = Comment.create(:title =>
params[:subject], :body => params[:plain])
render :text => 'success', :status => 200 #status of 404 rejects mail
end
end
Looking at the above comment controller, it looks like I'll need the job_id, current_user.id, and current_user.login.
Here's what I am having trouble sorting out: what goes in my "incoming_mails" controller? How can I make sure that when a user responds via email that the controller in "incoming_mails" is able to find the job_id and the current_user.id (or user_id) and then insert that information into the database?
I'm thinking that I'll need to grab the user's email address and then also have the job_id in the subject line...hmmm....
Does anyone have experience setting up incoming mail processing in Rails 3?
There's a few ways to do this. A common technique is to set the from/reply_to as a custom email address that allows you to do a lookup of the original object. Something like:
class Comment < ActiveRecord::Base
belongs_to :commentable
has_many :comments, :as => :commentable
before_validation :generate_token, :on => :create
validates :token, :presence => true, :uniqueness => true
attr_accessible :token
private
def generate_token
...
end
end
Email is sent with a from/reply_to address like [token]#msg.yoursite.com
(You can also use a comments+[token_or_id]#msg.yoursite.com if you'd like - see the :disposable param supplied by cloudmailin)
class IncomingMailsController < ApplicationController
def create
#comment = Comment.find_by_token(params[:to].split('#')[0])
#comment.comments.create(:body => params[:plain])
render :text => 'success', :status => 200
end
end
If you go the [token]#msg.yoursite.com then you have to setup your dns records properly as described here.
Another option is to store content in the headers of the email. Maybe your headers would look something like this:
X-YOURAPP-OBJECT-ID = 44
X-YOURAPP-OBJECT-TYPE = Job
X-YOURAPP-TARGET-ASSOC = comments
X-YOURAPP-TARGET-ATTR = body
Then your controller would look more like this:
class IncomingMailsController < ApplicationController
def create
headers = Mail::Header.new(params[:message])
object= headers[:x_yourapp_object_type].constantize.find(headers[:x_yourapp_object_id])
object.send(headers[:x_yourapp_target_assoc]).create(headers[:x_yourapp_target_attr] => params[:plain])
render :text => 'success', :status => 200
end
end
The advantage to doing it this way is that it's completely generic. You can have comments on jobs, comments on comments, or whatever. If you create another model that you also want to allow email reply on... just update the headers and you're set. The only issue is that you have to be careful of these headers being stripped out by mail clients. Take a look at this thread for more information about that.
I'm currently using the first technique and it's working very well. However, I'll be refactoring and trying the second technique at the end of this week.
I'm not sure this is a full answer but let me give it a quick shot and I can update it when I have a little more time.
Firstly I would start by having the job perhaps as part of the to address. Use comment+1#domain.com with 1 signifying that this is job number one. This part after the plus is called the is the disposable part in CloudMailin (params[:disposable]. The users email address can then be taken from the message body either using params[:from] or Mail.new(params[:message]).from (sometimes the from address given to the SMTP server is different to the one in the header)
Next you have the comment itself which can simply be the plain param.
This gives something like the following:
def create
#job = Job.find(params[:disposable])
if comment = #job.comments.create(:text => params[:plain], :commenter => User.find_by_email(params[:from])
render :text => 'Success', :status => 401
else
render :text => comment.errors.inspect, :status => 422, :content_type => 'text/plain'
end
I hope that helps. It's a bit rough but hopefully it gives the right idea.

Is there a way to check if a record was built by another model in active record?

When using accepts_nested_attributes_for, I got stuck when having a validation which required the original to be present. The code will help clear up that sentence.
class Foo < ActiveRecord::Base
has_one :bar
accepts_nested_attributes :bar
end
class Bar < ActiveRecord::Base
#property name: string
belongs_to :foo
validates_presence_of :foo #trouble line!
end
#now when you do
foo = Foo.create! :bar_attributes => {:name => 'steve'}
#you get an error because the bar validation failed
I would like to write a validation that goes something like...
class Bar < ActiveRecord::Base
validates_presence_of :foo, :unless => :being_built_by_foo?
end
I am currently using rails3.beta4
Thank you
Alas I don't have an answer to this post, but the I came up with another way so I didn't need the validation.
Since bar should never be without a foo then any request to create a bar without a foo_id is an error. In the real example a foo is a project, and bar is a bid. It is a nested resource, but I wanted to give access to json apps to be able to query the info from the /bids location so the router looked like.
resources :bids
resources :projects do
resources: bids
end
and then I just had to make sure all html access used project_bids_path or form_for [:project,#bid] etc. This next part is largely untested but so far the desired behavior is there. I got the idea from Yehuda's post on generic actions http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/
#I'm sure there is a better way then map.connect
map.connect "projects/invalid_id", :controller => "projects", :action => "invalid_id"
resources :projects
resources :bids
end
#couple of changes from Yehuda
def redirect(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc {|params| path % params }
status = options[:status] || 301
lambda do |env|
req = Rack::Request.new(env)
#Get both the query paramaters and url paramaters
params = env["action_dispatch.request.path_parameters"].merge req.params
url = path_proc.call(params.stringify_keys)
#Doesn't add the port back in!
#url = req.scheme + '://' + req.host + params
#content-type might be a bad idea, need to look into what happens for different requests
[status, {'Location' => url, 'Content-Type' => env['HTTP_ACCEPT'].split(',').first}, ['Moved Permanently']]
end
end
def bid_path
redirect do |params|
if params['project_id']
"/projects/#{params['project_id']}/bids/#{params['id']}"
else
'/projects/invalid_id'
end
end
end
match "bids", :to => bid_path
match "bids/:id", :to => bid_path
however, after doing all of this I most definitely don't think it worth it. I think nested_attributes breaks things and can be improved if that validation doesn't work, but after looking through the code for a little while I'm not sure exactly how to fix it or if it's worth it.
first of all, when using nested_attributes, you'll get the presence of the container. in the example: when you save Foo and there's also a nested form for Bar, then Bar is built by Foo.
I think there's no need to make this kind of validation if you're sure to use Bar only in contexts with Foo.
btw, try to write validation as follow (new preferred syntax for Rails3):
validates :foo, :presence => true
hope this helps,
a.

Resources