Rails 3: Validations for object - ruby-on-rails-3

I'm creating an object via controller. A Foo has many Bars. The Bar model has a validation that it must have a Foo in order to be valid.
In the foo.rb model:
has_many: bars
In the bar.rb model:
validates_presence_of :foo
belongs_to: foo
In the foo_controller.rb:
#foo = Booking.new(params[:foo]) # this has all the params needed to make a Foo.
#foo.create_bars(params[:bars])
In the foo.rb model:
def create_bars(bars)
bars.each do |t|
bar = Bar.create({name: name, email: email, foo: foo})
bar.foo = self
self.bars << bar
bar.save
puts self.bars.to_s
end
end
The puts self.bars.to_s
This sounds like it should be something really basic, but since the foo doesn't exist in the db, does ActiveRecord consider it to be nil, and that's why it's not saving? How can I write this properly?

Maybe try this:
def create_bars(bars)
bars.each do |t|
self.bars << Bar.new({name: t[:name], email: t[:email]})
end
end
<< operator used for active record objects sets association automatically.
Do not forget to add has_many :foos, and belongs_to :bar in the models.

I'll answer this part first:
This sounds like it should be something really basic, but since the
foo doesn't exist in the db, does ActiveRecord consider it to be nil,
and that's why it's not saving? How can I write this properly?
Not quite. The validation doesn't depend on anything in the database, it merely checks whether the specified field (or dependent object) is present in the model before persisting. The create method is a kind-of shortcut that not only initializes an ActiveModel object, but also simultaneously attempts to save it in the database. Your validation is failing because you're calling this method BEFORE you've set the foo field of your bar object.
You can technically use create in this instance by passing foo: self in your scenario, but frankly I would go with #pablopablo89's answer in regards to this part. If you do it this way, when you save Foo, all the Bar objects will also get saved.
Additionally, the .create method for creating Bar objects is dangerous because, since you're immediately persisting Bar objects independent of the parent Foo, if for whatever reason your Foo cannot be saved (fails some other validation, etc etc), you end up with a bunch of orphaned Bar objects with no way to remove them independent of a Foo (I'm making a bit of an assumption of how your system actually works). Assuming this is a reflection of your system, the goal you want to keep in mind is that an object, and all of its dependencies, are saved in one atomic operation. If one fails, they all fail, and you alert the user.
To answer the question more generally, as #phoet pointed out in the comment, you might be a lot better off changing your form and using the accepts_nested_attributes_for method. Here is the link to the rails documentation.
In the Foo model, add the line accepts_nested_attributes_for :bars, and then use fields_for in the form, something like this:
<%= form_for #foo do |f| %>
...other stuff...
<%= f.fields_for :bars do |f_bar| %>
... bar-related stuff ...
The submitted parameter map will return with all the bar-related stuff within the :foo key in such a way that it will create both the Foo object, and all of the related Bar objects all at once. In that scenario, create will also work, although I still tend to do separate .new and .save, but its up to you.
This link regarding how to use fields_for might be helpful as well.

Booking.new just initializes an object. In order for foo to exist you need to save it. Once you save it your validation while creating bar objects will pass and the objects should be created. Hopefully this should work

Related

Issue with pushing additional values in a embeds_many mongoid relation

I have been breaking my head around this for a long time now. Not sure if my approach is correct or if its not possible using mongoid. SO without further adieu, here is the problem:
I have the following 2 models:
def user
embeds_many :needs, :class_name => "Property"
embeds_many :skills, :class_name => "Property"
end
def property
end
Both these models of course have other code but I have skipped that for brevity.
With this structure I am able to access/add "Property" data as embedded "needs" & "skills" on my user model. Something like this works flawlessly
User.first.update_attributes(skills: [Property.first])
The problem is something like this doesn't work.
User.first.skills.push(Property.first)
User.first.skills << Property.first
There is no error. Both the above statements return true on console. But the values don't persist to the DB.
I basically want a Property model which can be maintained/created independent of the User model, thats why the "embedded_in" on Property is missing in my code.
The question is, am I doing it right? Or there is their a different way that I should go about the design of these models?
Cage is right. You will need to put the embedded_in on the Property model if you want the persistence to work properly. If you want to manage the lifecycle of Property outside the User model, you will have to use 'has_many'.
Please add more details as to what exactly is the purpose of doing what you are doing. I am going to make some assumptions-
Needs and skills are a global list of values, that should be maintained separately
User can have a subset of skills and needs
You want to store the needs and skills as 'plain string' and not references so you can query them without referencing the needs and skills collection. This reduces one query
If the above is correct, then you can use custom keys to store the needs and skills-
class Property
include Mongoid::Document
field :text, :type => String
field :_id, type: String, default ->{ text }
end
class User
include Mongoid::Document
has_many :needs, :class_name => "Property"
has_many :skills, :class_name => "Property"
end
Now you can do something like-
User.first.need_ids
This will give the text of the need and you can avoid another query.
Note- that this is potentially very risky if your 'Property' objects are mutable.
For solution try doing this
u = User.first
u.skills.push(Property.first)
u.skills << Property.first
it will work fine.

Rails - uncached controller action messing with serialized attribute

I have a controller action that calls a model method which generates a serialized list of data pulled from another model database. I need this to be uncached because the SQL queries should be random data pulls.
Here's a general idea of my code (Note that User has_one Foo, Bar is an arbitrary model of data, :data_list is of type text, and the database is SQLite):
# app/models/foo.rb
class Foo < ActiveRecord::Base
serialize :data_list
def generate_data
list = []
for i in 1..4
data = Bar.find(:first, :order => "Random()")
list << data
end
self.data_list = list
end
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def generate_action
...
uncached do
#user.foo.generate_data
end
#user.foo.save
end
end
# app/views/user/show.html.erb
...
<% #user.foo.data_list.each do |data| %>
<%= data %><br />
<% end %>
Whenever uncached do ... end is removed, everything works fine and the show view prints out each set of Bar objects in #user.foo.data_list. Unfortunately, because of Rails' SQL caching, it ends up look like this:
RandomDataPoint8
RandomDataPoint8
RandomDataPoint8
RandomDataPoint8
When I need to look like this:
RandomDataPoint7
RandomDataPoint13
RandomDataPoint2
RandomDataPoint21
It should be noted that running user.foo.generate_data from Rails command line works perfectly with the randomization. It is only when being called from the controller that caching starts to occur.
My research suggested I use uncached in the controller to remove caching, however it seems to destroy my data serialization and I receive the error:
undefined method 'each' for #<String:0x007ff49008dc70>
In fact, it does this even if I retroactively add in uncached (having successfully generated a data_plan without uncached prior) and save the controller, but don't call generate_action.
EDIT
I believe this problem is actually related to the fact that I was storing an object in the hash. Switching to the object id fixed this problem. Another SO question of mine regarding this can be found here:
Rails - Accessing serialized data from console
The following has been preserved just because the syntax may still help people, but I don't believe it was the actual cause of the problem.
I solved this by moving uncached to the model. For reference, the source I was using to originally solve this problem was this link: http://railspikes.com/2008/8/18/disabling-activerecord-query-caching-when-needed
What I overlooked is that he puts uncached in the model, not the controller. Also, the syntax needed to be a little different:
# app/models/foo.rb
self.class.uncached do
...
end
instead of
uncached do
...
end
The source for the syntax correction is this SO response: https://stackoverflow.com/a/967690/337903

Baffled by results of the render command in Rails

Lets say you have a post with comments on the same page, and you render a form for capturing a new comment also on the same page as you are displaying the post/comments. A post has_many comments. Code as follows:
class PostsController < ApplicationController
...
def show
#post = Post.find(:params[id])
#comment = Post.comments.new
end
...
end
Now when you call <%= #post.comments.count %> in your views it gives the number of comments that have been saved, but if you call <%= render #post.comments %> it returns all the saved comments PLUS the newly created (but not yet saved and therefore still invalid) comment. Why is this? This has really taken me time to find this and I can't imagine that this would be useful, why not just render all the valid database records?
Has anyone else ran into this? Easy to fix but puzzling..
Well, #post.comments.count actually does a database query and can therefore only return the number of saved records. (Use #post.comments.size or .length) for the number of objects in your collection.
The render call, AFAIK, only loops over the objects in the collection.
The thing to know here is the difference between when you do actual queries with the association, and when active record is using the cached objects. It is perhaps easy to assume that the comments in #post.comments is just an Array. It actually is a fancy proxy object that, depending on method called and state of the cached collection, acts like an Array or as an interface to the Model's query methods.

Track dirty for not-persisted attribute in an ActiveRecord object in rails

I have an object that inherits from ActiveRecord, yet it has an attribute that is not persisted in the DB, like:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
I would like to be able to track changes to 'bar', with methods like 'bar_changed?', as provided by ActiveModel Dirty. The problem is that when I try to implement Dirty on this object, as described in the docs, I'm getting an error as both ActiveRecord and ActiveModel have defined define_attribute_methods, but with different number of parameters, so I'm getting an error when trying to invoke define_attribute_methods [:bar].
I have tried aliasing define_attribute_methods before including ActiveModel::Dirty, but with no luck: I get a not defined method error.
Any ideas on how to deal with this? Of course I could write the required methods manually, but I was wondering if it was possible to do using Rails modules, by extending ActiveModel functionality to attributes not handled by ActiveRecord.
I'm using the attribute_will_change! method and things seem to be working fine.
It's a private method defined in active_model/dirty.rb, but ActiveRecord mixes it in all models.
This is what I ended up implementing in my model class:
def bar
#bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
#bar = value
end
def bar_changed?
changed.include?('bar')
end
The init_bar method is just used to initialise the attribute. You may or may not need it.
I didn't need to specify any other method (such as define_attribute_methods) or include any modules.
You do have to reimplement some of the methods yourself, but at least the behaviour will be mostly consistent with ActiveModel.
I admit I haven't tested it thoroughly yet, but so far I've encountered no issues.
ActiveRecord has the #attribute method (source) which once invoked from your class will let ActiveModel::Dirty to create methods such as bar_was, bar_changed?, and many others.
Thus you would have to call attribute :bar within any class that extends from ActiveRecord (or ApplicationRecord for most recent versions of Rails) in order to create those helper methods upon bar.
Edit: Note that this approach should not be mixed with attr_accessor :bar
Edit 2: Another note is that unpersisted attributes defined with attribute (eg attribute :bar, :string) will be blown away on save. If you need attrs to hang around after save (as I did), you actually can (carefully) mix with attr_reader, like so:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
#bar = val
end
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
Write the bar= method yourself and use an instance variable to track changes.
def bar=(value)
#bar_changed = true
#bar = value
end
def bar_changed?
if #bar_changed
#bar_changed = false
return true
else
return false
end
end

Do Rails 3 Active Record dynamic find_or_create finder methods have some undocumented inconsistencies?

Apologies for the long title, but this is bothering me. I'm new to Rails, so this is my first project. Rails 3.0.3.
In my model, a User may or may not have read many Entries; this is tracked in a model called ReadEntries. This many-to-one relationship is properly defined in the code, I think.
User.rb:
has_many :read_entries
Entry.rb:
has_many :read_entries
ReadEntry.rb:
belongs_to :entry
belongs_to :user
This table has to be populated at some point. If I try to do this:
user.read_entries.find_or_create_by_entry_id(entry.id, :read => false)
I get the error Unknown key(s): read. Leave out trying to set :read, and it works.
However, if I create the same row with this, it works:
ReadEntry.find_or_create_by_entry_id_and_user_id(entry.id, user.id, :read => false)
Logically, these methods should be identical, right? Thanks.
I've also had weird experiences with find_or_create. I would love it if it worked, but it seems inconsistent.
I'm currently having the same issue as you, and I think it may be due to calling find_or_create on an association as opposed to the model directly. Here's my example:
permission_assignments.find_or_create_by_role_id(:role_id => role_id, :is_allowed => false)
This works to create the assignment, except the "is_allowed" field gets set to it's default of "true". This code works for me (in the Permission model, hence the self reference)
PermissionAssignment.find_or_create_by_permission_id_and_role_id(:permission_id => self.id, :role_id => role_id, :is_allowed => false)
It's more verbose, unfortunately, but it works. The only problem that I still notice is that the object that is returned has no id assigned (the record does get created in the database, however, but if I wanted to update any more attributes I wouldn't be able to without the id). Don't know if that's a separate issue or not.
Rails 3.0.4 here with Postgres 8.4
You cannot pass in other fields like that as Rails will assume they are options for the find. Instead, you will need to make your method call longer:
user.read_entries.find_or_create_by_entry_id_and_read(entry.id, false)
Or alternatively use a shorter, custom syntax for that.
For your final example, my thoughts are that Rails will take the second argument and use that as options. Other than that, I am not sure.