Rails model how to reference column in a class method - ruby-on-rails

I currently have 2 models - Issue and Responses and i'm trying to add a method on the response model so I can do issue.responses.latest
I'm currently getting undefined method 'issue_id' for #<Class:...
How can I reference the column issue_id from the responses table in my self.latest method?
class Issue < ActiveRecord::Base
has_many :responses
end
class Response < ActiveRecord::Base
belongs_to :issue
def self.latest
select([:id, :user_id, :issue_id, :response, 'MAX(created_at)'])
.where(:issue_id => self.issue_id) <!-- How to reference the issue_id column here?
.group(:issue_id)
end
end

You're referencing the issue_id column fine. The problem is that you're trying to provide a value of self.issue_id, but there is no issue_id class method of Response.
Also, a latest class method for Response won't be invoked by issue.responses.latest, since responses is an <ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_Collec‌​tionProxy_Table:0x007f85220bd3b8>. You could, however, define an Issue instance method of latest_response.
On a related point, since responses is an Enumerable, you can do issues.responses.max_by {|response| response.created_at}

Related

What is the proper way to access virtual atributes in Ruby on Rails?

I have a model w/ a virtual attribute:
class Campaign < ActiveRecord::Base
def status
if deactivated
return "paused"
else
return "live"
end
end
end
now, in my view, when I access the attribute with campaign.status, I am getting the proper result. However, when I try to access it like this campaign[:status], I get nothing back.
Why is that?
[:status] uses the [] method in Ruby. 'def status' defines a method which shouldn't be mistaken with an ActiveRecord attribute or an virtual attribute (e.g. attr_reader or attr_accessor).
ActiveRecord adds the [] method to your class and makes all the (database) attributes accessible by object[:attr_name] AND object.attr_name(And even object.attributes[:attr_name]).
This is different from how f.e. Javascript works where obj[:method] is virtually the same as obj.method.
Edit: You should be able to use the attr_accessor if you use them for example in any form:
<%= form.input :status %>
Submitting the form will then set the instance variable #status. If you want to do anything with this before or after saving you can call an before_save or after_save hook:
class Campaign < ActiveRecord::Base
attr_accessible :status
attr_accessor :status
before_save :raise_status
def raise_status
raise #status
end
end
This will throw an error with the value submitted value for status.
Hope this helps.

Why validate function is not called in Rails3.2.9?

The codes are as follows:
class Seat < ActiveRecord::Base
attr_accessible :baggage, :flight_id, :name
def validate(record)
errors.add_to_base("You have too much baggage")
end
end
I expected it throws error whenever a new record is wriiten into the database.
However, nothing happened when new record wriiten into seats database by #seat.save
Does anyone have ideas about this?
validate(record) looks weird. You should try
class Seat < ActiveRecord::Base
attr_accessible :baggage, :flight_id, :name
validate :valid_baggage
def valid_baggage
errors.add_to_base("You have too much baggage")
end
end
Also note that you need to call #seat.save! (instead of #seat.save) in order to get the exception. #seat.save will return true or false only ... but that's usually what you want, so consider if you really want to raise an exception.
The record parameter is only needed if you are implementing a custom validator, otherwise the method already knows which record it is validating (self).

Proper way to do this with ActiveRecord?

Say I have two classes,
Image and Credit
class Image < ActiveRecord::Base
belongs_to :credit
accepts_nested_attributes_for :credit
end
class Credit < ActiveRecord::Base
#has a field called name
has_many :images
end
I want associate a Credit when Image is created, acting a bit like a tag. Essentially, I want behavior like Credit.find_or_create_by_name, but in the client code using Credit, it would be much cleaner if it was just a Create. I can't seem to figure out a way to bake this into the model.
Try this:
class Image < ActiveRecord::Base
belongs_to :credit
attr_accessor :credit_name
after_create { Credit.associate_object(self) }
end
class Credit < ActiveRecord::Base
#has a field called name
has_many :images
def self.associate_object(object, association='images')
credit = self.find_or_create_by_name(object.credit_name)
credit.send(association) << object
credit.save
end
end
Then when you create an image what you can do is something like
Image.create(:attr1 => 'value1', :attr2 => 'value2', ..., :credit_name => 'some_name')
And it will take the name that you feed into the :credit_name value and use it in the after_create callback.
Note that if you decided to have a different object associated with Credit later on (let's say a class called Text), you could do still use this method like so:
class Text < ActiveRecord::Base
belongs_to :credit
attr_accessor :credit_name
before_create { Credit.associate_object(self, 'texts') }
end
Although at that point you probably would want to consider making a SuperClass for all of the classes that belong_to credit, and just having the superclass handle the association. You might also want to look at polymorphic relationships.
This is probably more trouble than it's worth, and is dangerous because it involves overriding the Credit class's initialize method, but I think this might work. My advice to you would be to try the solution I suggested before and ditch those gems or modify them so they can use your method. That being said, here goes nothing:
First you need a way to get at the method caller for the Credit initializer. Let's use a class I found on the web called CallChain, but we'll modify it for our purposes. You would probably want to put this in your lib folder.
class CallChain
require 'active_support'
def self.caller_class
caller_file.split('/').last.chomp('.rb').classify.constantize
end
def self.caller_file(depth=1)
parse_caller(caller(depth+1).first).first
end
private
#Stolen from ActionMailer, where this was used but was not made reusable
def self.parse_caller(at)
if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
file = Regexp.last_match[1]
line = Regexp.last_match[2].to_i
method = Regexp.last_match[3]
[file, line, method]
end
end
end
Now we need to overwrite the Credit classes initializer because when you make a call to Credit.new or Credit.create from another class (in this case your Image class), it is calling the initializer from that class. You also need to ensure that when you make a call to Credit.create or Credit.new that you feed in :caller_class_id => self.id to the attributes argument since we can't get at it from the initializer.
class Credit < ActiveRecord::Base
#has a field called name
has_many :images
attr_accessor :caller_class_id
def initialize(args = {})
super
# only screw around with this stuff if the caller_class_id has been set
if caller_class_id
caller_class = CallChain.caller_class
self.send(caller_class.to_param.tableize) << caller_class.find(caller_class_id)
end
end
end
Now that we have that setup, we can make a simple method in our Image class which will create a new Credit and setup the association properly like so:
class Image < ActiveRecord::Base
belongs_to :credit
accepts_nested_attributes_for :credit
# for building
def build_credit
Credit.new(:attr1 => 'val1', etc.., :caller_class_id => self.id)
end
# for creating
# if you wanted to have this happen automatically you could make the method get called by an 'after_create' callback on this class.
def create_credit
Credit.create(:attr1 => 'val1', etc.., :caller_class_id => self.id)
end
end
Again, I really wouldn't recommend this, but I wanted to see if it was possible. Give it a try if you don't mind overriding the initialize method on Credit, I believe it's a solution that fits all your criteria.

accepts_nested_attributes_for :reject_if to trigger another method

I've got a multi-level nested form using formtastic_cocoon (jquery version of formtastic).
I am trying to do some validation in the sense of
if value is_numeric do
insert into database
else do
database lookup on text
insert id as association
end
I was hoping tha the accepts_nested_attributes_for would have an :if option, but apparently there is only the :reject_if.
Is there a way to create a validation like I describe as part of the accepts_nested_attributes_for??
-----------------------Updated as per Zubin's Response ---------------------------
I believe Zubin is on the right track with a method, but I can't seem to get it working just right. The method I am using is
def lookup_prereq=(lookup_prereq)
return if lookup_prereq.blank?
case lookup_prereq
when lookup_prereq.is_a?(Numeric) == true
self.task_id = lookup_prereq
else
self.task = Task.find_by_title(lookup_prereq)
end
end
When I trigger this function, the self.task_id is being put in the database as '0' rather than the Task.id.
I'm wondering if I'm missing something else.
I'm not completely sure that the method is actually being called. Shouldn't I need to say
lookup_prereq(attr[:prereq_id)
at some point?
-------------------further edit -----------------------
I think from what I can find that the method is called only if it is named with the same name as the value for the database, therefore I've changed the method to
def completed_task=(completed_task)
Unfortunately this is still resulting in 0 as the value in the database.
Sounds like you need a method in your nested model to handle that, eg:
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :author
def lookup_author=(lookup_author)
return if lookup_author.blank?
case lookup_author
when /^\d+$/
self.author_id = lookup_author
else
self.author = Author.find_by_name(lookup_author)
end
end
end

Proxy Objects with ActiveRecord models - method_missing not working sometimes

I've been using a model of my application as a proxy to other objects that define behavior.
class Box < ActiveRecord::Base
belongs_to :box_behavior, :polymorphic => true, :validate => true, :foreign_key => 'box_behavior_id', :dependent => :destroy
[...]
def initialize(opts = {})
super(opts)
self.box_behavior = BoxBehaviorDefault.new if self.box_behavior.blank?
end
private
def method_missing(method, *args, &block)
super
rescue NoMethodError
return self.box_behavior.send(method,*args,&block)
end
end
So I implement all the methods on my BoxBehavior objects, and when I call a method on a box instance then it redirects the call to the associated boxbehavior object. It all works fine except when i tried to create a hook on my purchase model where it gets the total from its box object and saves it:
class Purchase < ActiveRecord::Base
belongs_to :box
before_validation_on_create { |r| r.total = r.box.total }
end
When I try to save any purchase object that has a box associated, I get this error:
undefined method `total' for #<ActiveRecord::Associations::BelongsToAssociation:0x7fe944320390>
And I don't have a clue on what to do next... When I implement the total method directly in the box class then it works fine... what can I do to solve this? Isn't the proxy working properly?
I found out that Rails doesn't always use initialize to create a new instance of a model. So i used the hook after_initialize and solved the problem!

Resources