Number validation - reference to another attribute - ruby-on-rails-3

I need a validation to check whether an attribute is less or equal than another (virtual) attribute of the same record. How can I do that?
Sample code (not working - NoMethodError):
attr_reader :virt
attr_accessible :virt
validates :my_attr, :numericality => {:only_integer => true, :less_or_equal => self.virt}
(please be gentle and explicit, I'm a RoR newb :])

Since those validation lines are going to be executed when the class definition is first encountered, self.virt doesn't exist.
You can usually pass in a lambda/proc instead that will be called at validation time from the scope of the object:
validates :my_attr, :numericality => { :less_or_equal => lambda { virt } }
This still isn't that great, though. A better route would be to just define your own validation method:
validate :my_attr_is_within_range
def my_attr_is_within_range
my_attr <= virtual_attribute
end
This is much cleaner and more explicit. Note that you don't need to use self here since there is no ambiguity (if you were setting you would need the self).

Related

Rails Routing: One controller. One model with type. Multiple routes

I have one model named Factors, which has two types: ['personal', 'advisor']
I want to have one controller FactorsController that has all the same actions for both types of Factors, but only ever uses one type. The type that it uses is based on the route used to get there. For example,
/personal would route to factors#index and populate #factors with Factor.personal
/advisors would route to factors#index and populate #factors with Factor.advisors
How would I go about setting this up?
You can add to the routes
type_regexp = Regexp.new([:personal, :advisor].join("|"))
resources :factors, path: ':type', constraints: { type: type_regexp }
and you will be able to user params[:type] in the controllers, that gives you flexibility in case you wanna changes the routes in the future.
This also gives you the ability to use factors_path(type: :personal) in the views.
You can add this to your routes:
resources :factors, :path => :personal
resources :factors, :path => :advisor
This will then have both /personal and /advisor. You'll then want to have factors#index determine which path was used (you could use request.url) and populate #factors accordingly.
I would create three controllers:
class PersonalController < FactorController
def factor
Factor.personal
end
end
class AdvisorController < FactorController
def factor
Factor.advisors
end
end
class FactorController < ApplicationController
#all the shared stuff here, using the factor method from each in your methods
end
and then the routes would be:
route '/personal' => PersonalController#index
route '/advisors' => AdvisorController#index

Calling ActiveRecord::Create() from within rails user model not associating new object with user

In my rails user model, I am trying to write a method which will return a list for the current time frame, and in the absence of a list for that time frame, create one which is associated with the user and then return it:
class User < ActiveRecord::Base
def todaysList
today = Time.new
if self.lists.where(:date => today.to_date)
return self.lists.where(:date => today.to_date).first #Get the object, not the ActiveRecord::Relation
else
self.lists.create!(:date => today.to_date) #Make the list, return it!
end
end
My question is, why is it that when I call self.lists.create!(:foo => 'bar'), the user association is not populated?
I've decided to get around this a more sloppy way, by explicitly assigning the user in the create! call, as such:
self.lists.create!( :date => today.to_date, :User_ID = self.id)
however this solution just doesn't seem right.
Thanks in advance and apologies as always for stupid, redundant or badly worded questions.
I would do something like this:
def todays_list
lists.find_or_create_by_date(Date.today)
end
The method name change is just preference.

Is this a case for inheritance?

I have Rails models User, ReadingList and SessionReadingList. A user has many reading lists. A SessionReadingList is a special type of reading list for before a user has registered, stored in the session.
In my ReadingListsController every action is of the form:
def show
if current_user
#load user's reading lists
else
#load session reading list from session
end
end
I'm wondering whether I'd be better off subclassing ReadingListsController so I have e.g. SessionReadingListsController and UserReadingListsController. I don't know how I'd handle the routing then though.
So, is subclassing the solution? If so, do I redirect from the ReadingListsController depending on current_user? Or is there a better way?
You can create a custom route matcher that uses the appropriate controller.
class LoggedInConstraint < Struct.new(:value)
def matches?(request)
request.cookies.key?("user_token") == value
end
end
match 'reading-list' :to => "reading_list#index", :constraints => LoggedInConstraint.new(true)
match 'reading-list' :to => "session_reading_list#index", :constraints => LoggedInConstraint.new(true)

Rails, creating a callback

I want to use an ActiveModel callback to be called after an object has been voted on, the issue is that the gem I'm using (voteable_mongo) to make the model votable doesnt provide like a vote model or callback in my app, so how can I create a callback for it?
set_callback(:vote, :before) do |object|
object.do_something
end
Obviously that vote action I made up, but the gem I'm using has this method, how would you properly extend this method to trigger a callback?
Taking the plugin example as source here's what you could do:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
def vote(options, value = nil)
_run_vote_callbacks do
super( options, value )
end
end
end
I did not run this code so I am not sure if this is going to work correctly or not, but in the worst case you could alias the vote method using alias_method_chain or just copy and paste the source to inside the _run_vote_callbacks block (really, really ugly, but it's a solution anyway).
EDIT
This could also be done using alias_method_chain, if the code above does not work:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
alias_method_chain :vote, :callback
def vote_with_callback(options, value = nil)
_run_vote_callbacks do
vote_without_callbacks( options, value )
end
end
end

Rails 3 - Pass a parameter to custom validation method

I am looking to pass a value to a custom validation. I have done the following as a test:
validate :print_out, :parameter1 => 'Hello'
With this:
def print_out (input="blank")
puts input
end
When creating an object or saving an object, the output is 'blank.' However, if called directly:
object.print_out "Test"
Test is instead outputted. The question is, why is my parameter not passing properly?
Inside the 'config\initializers\' directory, you can create your own validations. As an example, let's create a validation 'validates_obj_length.' Not a very useful validation, but an acceptable example:
Create the file 'obj_length_validator.rb' within the 'config\intializers\' directory.
ActiveRecord::Base.class_eval do
def self.validates_obj_length(*attr_names)
options = attr_names.extract_options!
validates_each(attr_names, options) do |record, attribute, value|
record.errors[attribute] << "Error: Length must be " + options[:length].to_s unless value.length == options[:length]
end
end
end
Once you have this, you can use the very clean:
validates_obj_length :content, :length => 5
Basically, we reopen ActiveRecord::Base class and implement a new sub-validation. We use the splat (*) operator to accept an array of arguments. We then extract out the hash of options into our 'options' variable. Finally we implement our validation(s). This allows the validation to be used with any model anytime and stay DRY!
You could try
validate do |object_name|
object_name.print_out "Hello"
end
Instead of your validate :print_out, :parameter1 => 'Hello'.