How does Ruby on Rails work with an "inclusion" validation? - ruby-on-rails

I am using Ruby on Rails v3.0.9 and I have the following code in a my model:
class User < ActiveRecord::Base
validates :users_role,
:inclusion => {
:in => UserRole.all.map(&:role)
},
:presence => true
end
When I browse an UserRole related page (that is, a page where an user object instance is involved - for example, the RoR conventional "show" or "index" views), and I inspect the log file I see that the UserRole SQL query is performed. That is, the UserRole.all.map(&:role) run.
I would like to know how Ruby on Rails works for the above case. It concerns the performance? Is the UserRole.all.map(&:role) lazy loaded?

In your dev environment you will probably see that query running on any request which validates user, as code is reloaded on each request. Try it in production mode once, it should not happen in that case, as code is loaded once only and you haven't put the query in a lambda. There should be no lazy loading here as you already called map on the resultset.

Related

Ruby on Rails - inconsistency when needing to reload model?

I found something which seems a bit confusing to me being new to Rails. I was told I need to do reload! in the console whenever I made a change in the model.
Let's assume I call reload! before these two senarios.
Let's say I have scenario A, with a model with a specific syntax error as such:
class Article < ActiveRecord::Base
validator :title, presence: true
end
Running Article.new(title: "Test 1") will throw a NoMethodError, as it understandably would. But if I then go in and fix the error, even if I don't run reload!, running Article.new(title: "Test 1") works now.
Scenario B, going in the opposite direction. I have a model with the correct syntax as such:
class Article < ActiveRecord::Base
validates :title, presence: true
end
Running Article.new(title: "Test 1") will work with no error, as it should. But if I then go in and change validates to validator. If I don't run reload!, running Article.new(title: "Test 1") still works despite the article.rb file having a syntax error. It isn't until I run reload! explicitly that I now get a NoMethodError.
What's exactly going on here? I know it's very specific, but I don't see why this would be the case. It seems like sometimes you have to run reload! to update the model (like scenario B) and sometimes, like in scenario A, you don't.
In your first example, Rails was not able to load the class because it raised an error. After fixing the error there was no need to class reload because the class was not loaded successfully before.
In your second example, the class was loaded successfully. Therefore you need to call reload! to tell Rails to reload the class into memory.

Why won't rails create associated records/objects from nested form using strong parameters?

I'm trying to create a record and it's associated records from a nested form using strong parameters. My primary model is:
class MaterialDonationRequest < ActiveRecord::Base
has_many :donation_items, dependent: :destroy
accepts_nested_attributes_for :donation_items, allow_destroy: true
validates :name, presence: true
attr_accessor :due_on_event, :date, :donation_items_attributes, :event_id
end
My associated (nested) model is:
class DonationItem < ActiveRecord::Base
validates :name, presence: true
belongs_to :material_donation_request
belongs_to :global_profile
validates :name, presence: true
attr_accessor :_destroy
end
In my material_donation_requests_controller.rb, I have the following for strong parameters:
def material_donation_request_params
params.require(:material_donation_request).permit(:name, :description, :event_flag, :due_on_event, :date, :event_id, donation_items_attributes: [:id, :name, :description, :amount, :_destroy])
end
Here's the line in my create method where I create the object:
#material_donation_request = MaterialDonationRequest.new(material_donation_request_params)
After doing this, #material_donation_request is created and populated correctly from the form. But the associated donation_items do not get created. For instance, in the debugger, when I enter #material_donation_request.donation_items.first, Rails returns nil.
For reference, here is what Rails returns for material_donation_request_params in the manual tests I'm running:
{"name"=>"Name", "description"=>"", "due_on_event"=>"true", "date"=>"", "donation_items_attributes"=>{"0"=>{"name"=>"", "amount"=>"1", "_destroy"=>""}, "1427122183210"=>{"name"=>"", "amount"=>"2", "_destroy"=>""}}}
Why isn't Rails creating the associated objects from the form as well? Everywhere I've looked, it seems like this structure should create everything, and a subsequent save should save everything (or at least throw validation errors as in this case-see update below). Is there something I'm missing?
Update
Since it was brought up in the answers, yes, the material_donation_params shown above would not pass validation. That's the scenario I've been manually testing. It should generate a validation error on save, but instead, simply saves the MaterialDonationRequest with no errors of any kind, and saves nothing to DonationItems.
To be clear, though, if I fill out the form completely and get the following material_donation_request_params:
{"name"=>"Name", "description"=>"", "due_on_event"=>"true", "date"=>"", "donation_items_attributes"=>{"0"=>{"name"=>"first", "amount"=>"1", "_destroy"=>""}, "1427122183210"=>{"name"=>"second", "amount"=>"2", "_destroy"=>""}}}
and then do #material_donation_request.save, it only saves the MaterialDonationRequest, and not any of the DonationItems.
Final Update
Okay. I've deleted my previous "final update" because what I wrote, and what I wrote in some of the comments was wrong. What ended up fixing this was not an update to Rails 4.1.8. I ran the bundle update command before actually saving the gem file with the new Rails version. So really, what ended up fixing this was simply updating all the gems that didn't have fixed version numbers. God only knows why things weren't working with the previous set of gems. Sorry that this isn't so helpful...
From Rails Validations guide
presence
This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.
You are requiring donation_item to be present, but your resulting params hash clearly has donation names blank, validation is failing. Calling save! when debugging these things can be helpful since it would throw a error on failure.
I figured out the answer. In total desperation, I upgraded my Rails version from 4.0.2 which is what I had been using, to 4.1.8. After doing this, with no other changes whatsoever (except gem dependencies, of course), it just started working the way it's supposed to. So I guess Rails 4.0.2 has a problem with nested forms and strong parameters.

Rails simple validations not working

class User < ActiveRecord::Base
attr_accessible :email, :name
validates :name,:presence=>true,
:length=>{:maximum=>15}
validates :email,:presence=>true,
:length=>{:maximum=>15}
end
I am new to rails and the simplest of the validators are not working. I think I may be making a very silly mistake . I have a User model with 2 attributes only and when I create a new user in ruby console with wrong validations like no name or a longer name than 15 characters it gets added happily Please suggest.I am using rails version:3.2.13 and ruby version:1.9.3
If you are on rails console, be sure to type reload! after making changes to models. In this way, all changes will be reloaded in the console instance.
Moreover, are you sure you are saving these models? You should try something like this:
user = User.new(email: "john.doe#gmail.com")
user.save
If the result of the last line is false, you can view the validation errors with
p user.errors

Rails validatation to ensure a username does not clash with an existing route?

I want to ensure users can't create usernames that clash with my existing routes. I would also like the ability to deny future routes I may define. I am thinking of accomplishing this like so:
In the model:
class User < ActiveRecord::Base
##invalid_usernames = %w()
cattr_accessor :invalid_usernames
validates :username, :exclusion { :in => ##invalid_usernames }
end
In some initializer:
User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
Is this "Rails way"? Is there a better way?
Here's my own answer, tested and working with Rails 3.1.3 and Ruby 1.9.3
app/models/user.rb
class User < ActiveRecord::Base
class_attribute :invalid_usernames
self.invalid_usernames = Set.new %w()
validates :username, presence: true,
uniqueness: { case_sensitive: false },
exclusion: { in: lambda { self.invalid_usernames }}
end
config/application.rb
[:after_initialize, :to_prepare].each do |hook|
config.send(hook) do
User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
end
end
Notes
I initially tried setting User.invalid_usernames during after_initialize but found it needs to be set during to_prepare (i.e. before each request in development mode, and before the first request in production mode) since the models are reloaded in development before each request and the original setting is lost.
I am however also setting User.invalid_usernames during after_initialize because the routes don't seem to be available during to_prepare when running in the test environment. Another workaround I tried for this, which does work, is to force load the routes during to_prepare:
config.to_prepare do
Rails.application.reload_routes!
User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
end
I like this because it's DRY and easy to read. But I'm wary of reloading the routes on every request, even if it is only in development mode. I'd rather use something a little harder to read if it means I fully understand the impact. Open to criticisms!
I also ditched cattr_accessor for class_attribute when I found out the former applies to the entire class hierarchy (i.e. changing its value on a subclass would affect the superclass)
I also chose to use a Set for User.invalid_usernames instead of an array as there's no need to store and compare against dupes and it was a transparent change.
I also changed to Ruby 1.9 hash syntax (:

Parent model method is undefined

I'm an absolute beginner to Ruby on Rails; I literally started last night. I'm following the Getting Started with Rails tutorial, although I've made a few modifications. Instead of a blog with posts and comments, I'm making a simple task-tracking application in which Projects have Tasks associated with them.
Everything was going quite well until step #9 (Deleting Comments). According to the tutorial, I should be able to get the parent model for a comment by calling comment.post, the analogue of which in my case is task.project. But when Rails tries to render the partial in which I've got task.project, it raises a NoMethodError:
undefined method `project' for #<Task:0x7fb0011cf058>
My models are as follows:
class Project < ActiveRecord::Base
validates :name, :presence => true
has_many :tasks, :dependent => :destroy
end
class Task < ActiveRecord::Base
validates :name, :presence => :true
belongs_to :project
end
Using the rails console, I determined that my tasks do have a project_id method which returns the ID of the associated project. Thus, I can get around the problem by using Project.find(task.project_id) instead of task.project in my partial. That seems "wrong," though, and makes me wonder if the relationship between my two models is somehow broken.
What's going on here?
Update: If I comment out the validates line in the Task model, then all of its methods work properly. I don't understand why that is the case, though.
Update 2: Figured it out. I was using the symbol :true instead of the simple boolean value in the validates line of my Task model. Had I paid more attention to the stack trace in the first place, I would have figured it out much sooner. What an embarrassing mistake!
Your code looks perfect. But if you were trying that into console try reloading first and then try again
reload!
task = Task.find(<record-id>)
task.project
It should be working.

Resources