I have special method in array.rb, which I want to add to array's instance objects.
Basically, it iterates through collection of new records and saves it in one transaction.
##items.list_transaction
in controller.
include ActiveRecord
class Array
def list_transaction
errors=[]
ActiveRecord::Base.transaction do
self.each do |list_item|
unless list_item.save #&& yield(list_item).save
errors << list_item.errors
end
end
end
if errors.any?
errors
else
true
end
end
end
What I noticed, as this code is not part of controller/model, it does not handle associations.
For example, If I put exact same code inside of controller, on list_item.save it will also validate associated with list_item account, but as I place it in separate file, I need to add this commented part: yield(list_item).save where block refers to list_item.account.save, or Rails just skips this.
Same thing with errors.
Having this code as part of controller code, I would get list_item.errors contain list_item.account.errors, but that doesn't work in separate file.
I tried also
include ActiveRecord:Association
both inside and outside Array class (not sure if this matters), but it doesn't work out.
ActiveModel::Model is what you're looking for. It has already included ActiveModel::Validations.
But extending Array class with ActiveModel feels horribly wrong.
Related
I'm using mongoid for an app where the user is the parent document, and pretty much all other information is embedded in the user. So for instance, my controller #new action for a Relationship belonging to the user looks something like:
def new
#relationship = current_user.relationships.new(friend_id: params[:fid])
#relationship.validate
end
Because I run validations on the relationship that will show up in the view and some of those validations need to be able to reference the parent, I can't just call #relationship = Relationship.new(friend_id: params[:fid]), but having instantiated this relationship in the user's relationship array, it's now hanging out in there, even if the user decides they don't want to make a new relationship after all and they go to another part of the site. If they go to the relationship index page, they'll see it in the list unless I filter it out.
If the relationship is valid and they do something elsewhere that causes the user to save, that dummy relationship is now a real one. If it's not valid, the save is going to fail for unknown reasons.
I have a number of models I intend to embed in the user, so I will have this issue with every one of them.
I know I can call current_user.reload to clear the junk out, but it feels ridiculous to me that I would have to hit the database every time I wanted to do this. I could also orphan the relationship after validating, but that feels hacky.
It seems to me that this is a problem people should run into all the time with embedded documents, so I would think there'd be some kind of built in solution, but I can't seem to find it anywhere. I saw this question, which is similar to mine, but I want something more extensible, so that I don't have to put it everywhere.
I'm about to make a module that will add a clear_unsaved_#{relation} method to the class for each embedded relation, but the idea frustrates me, so I wanted to see if anyone has a better idea of how to do it, and also where is best to call it.
I ended up making a monkey patch that overrides Mongoid's embeds_many and embeds_one class methods to also define an instance method for clearing unsaved documents for that relation. This felt like the most straightforward way to me because it's very little code and it means I don't have to remember to include it places.
# config/initializers/patches/dirty_tracking_embedded.rb
module DirtyTrackingEmbedded
# override the embedding methods to also make dirty-tracking
def embeds_many(name, options= {}, &block)
define_method "clear_unsaved_#{name}" do
# remove_child removes it from the array without hitting the database
send(name).each {|o| remove_child(o) unless o.persisted?}
end
super
end
def embeds_one(name, options={}, &block)
define_method "clear_unsaved_#{name}" do
dirty = send(name)
remove_child(dirty) unless dirty.persisted?
end
super
end
end
module Mongoid
module Association
module Macros
module ClassMethods
prepend DirtyTrackingEmbedded
end
end
end
end
Then in my controller I resorted to an after_action:
# app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
after_action :clear_unsaved, only: [:new]
def new
#relationship = current_user.relationships.new(friend_id: params[:fid])
#relationship.validate
end
private
def clear_unsaved
current_user.clear_unsaved_relationships
end
end
Other Possibilities
Different monkey patch
You could monkey patch the setup_instance_methods! methods in Mongoid::Association::Embedded::EmbedsMany and Mongoid::Association::Embedded::EmbedsOne to include setting up an instance method to clear unsaved. You can find an example of how the Mongoid folks do that sort of thing by looking at Mongoid::Association::Accessors#self.define_ids_setter!. I'd recommend doing your patching with a prepend like in the solution I went with, so you can inherit the rest of the method.
Combo monkey patch and inheritance
Mongoid chooses which class to use to instantiate an association from a constant called MACRO_MAPPING in Mongoid::Association, so you could make classes that inherit from EmbedsMany and EmbedsOne with just setup_instance_methods! overridden to add the needed instance method, then you would only have to monkey patch MACRO_MAPPING to map to your new classes.
Concern
If you're anti-monkey patching, you could use the code from my DirtyTrackingEmbedded module to make an ActiveSupport::Concern that does the same thing. You'll want to put the overridden methods in the class_methods block, and then just make sure you include this module after you include Mongoid::Document in any model class you want it in.
so i've got one pretty simple method in a model here:
def log
self.statistics.build()
self.save
return
end
now i wanted to exclude this method into a module to use it in different models.
module Statistic
def log
self.statistics.build()
self.save
return
end
end
i added the file to the autoload paths and included it into my model (the inclusion works fine).
class Foo < ActiveRecord::Base
include Statistic
end
trying to call the .log method results in an error:
undefined methodnew' for Statistic:Modulethe raised line number is theself.statistics.build()` line.
any ideas why this is not working?
thanks for all hints! please leave a comment if something is unclear.
I think this is a naming clash.
It seems you have a has_many :statistics, by default this will look for a class called Statistic.
But this is the same name as the module you have created.
I suggest renaming your module to StatisticsExtensions or something of that sort.
The reason you're seeing a missing method for new is because build is an alias for new in the ActiveRecord source. It's assuming statistics is an active record relation, and it's not.
https://github.com/rails/rails/blob/d22592a05b299c21e30ec8b38890a178dca863b4/activerecord/lib/active_record/relation.rb#L83
So, I have a database of people on an external system, and I want to set up the code to easily create people records internal to our sysem based on the external system. The field names, of course, are not the same, so I've written some code which maps from one table to the next.
class PeopleController < ApplicationController
...
def new
#person = Person.new
if params[:external_id] then
initialize_from_external_database params[:external_id]
end
end
private
def initialize_form_external_database(external_id)
external = External::Person.find(external_id)
if external.nil?
...
else
#person.name_last = exteral.last_name
#person.name_first = external.first_name
#...
#person.valid?
end
end
end
Okay, so the stuff in the "else" statement I can write as a loop, which would use a hash something like:
FieldMappings = {
:name_last => :last_name,
:name_first => :first_name,
:calculated_field => lambda {|external_person| ... },
...
}
But where would you put this hash? Is it natural to put it in the External::Person class because the only reason we access those records is to do this initialization? Or would it go in the controller? Or a helper?
Added: Using Rails 2.3.5.
I'd put this code in the External::Person to avoid Person even having to know it exists. Use a 'to_person' method (or maybe 'to_internal_person') on External::Person. Keep the Hash in External::Person and use it to perform the generation. Either way as JacobM says, you want this code in your model, not controller.
class PeopleController < ApplicationController
def new
if external = External::Person.find_by_id params[:external_id]
#person = external.to_person
else
#person = Person.new
end
end
end
If you're in Rails 3.x (maybe also in 2.x, I'm not sure), you can put miscellaneous classes and modules in your /extras folder which is included in the autoloader path. This is where I always put things of this nature, but I' not aware of any Rails convention for this sort of thing.
First of all, I would do that work in your (internal) Person model -- give it a class method like create_person_from_external_person that takes the external person and does the assignments.
Given that, I think it would be OK to include the hash within that Person model, or somewhere else, as Josh suggests. What would be particularly cool would be to write a generic create_person_from_external_person method that would ask the external person for a hash and then do the mapping based on that hash; that approach could support more than one type of external person. But that may be overkill if you know this is the only type you have to deal with.
I wouldn't put it in the controller, but, again, I wouldn't do that work in the controller either.
You can put it on a module on the lib directory so you don't mess any of your classes that will be full of awesome code that will probably last many years. Another good reason is you can then include/require your mapping module everywhere you need it (maybe in your tests).
module UserMapping
FIELDS = { :last_name => :name_last, .... }
end
If you drop the module on the lib and you use rails 3 you should put this on your config/application.rb file:
config.autoload_paths += %W(#{config.root}/lib)
On Rails::VERSION::MAJOR < 3 the lib directory is automatically added to the autoload_path
When we try to deserialize a Model from our database we always receive a YAML object. For that we added the following code in the environment.rb:
YAML.add_domain_type("ActiveRecord,2007", "") do |type, val|
klass = type.split(":").last.constantize
YAML.object_maker(klass, val)
end
class ActiveRecord::Base
def to_yaml_type
"!ActiveRecord,2007/#{self.class}"
end
end
class ActiveRecord::Base
def to_yaml_properties
['#attributes']
end
end
This works! But only once, when I refresh the screen I always undefined method ... for YAML. It seems like my code isn't executed anymore...
Can anyone help?
Thnx!
it's not a good idea to serialize a full active record object. The object might change in the meantime and, when you load it, you might find yourself working with a stale object.
be sure the class definition of the object you are deserializing is loaded before the object is deserialized. Normally, you won't need to require the class because it's automatically loaded by Ruby when you try to use it. This doesn't happen when you deserialize an object.
Ok, so I've been refactoring my code in my little Rails app in an effort to remove duplication, and in general make my life easier (as I like an easy life). Part of this refactoring, has been to move code that's common to two of my models to a module that I can include where I need it.
So far, so good. Looks like it's going to work out, but I've just hit a problem that I'm not sure how to get around. The module (which I've called sendable), is just going to be the code that handles faxing, e-mailing, or printing a PDF of the document. So, for example, I have a purchase order, and I have Internal Sales Orders (imaginatively abbreviated to ISO).
The problem I've struck, is that I want some variables initialised (initialized for people who don't spell correctly :P ) after the object is loaded, so I've been using the after_initialize hook. No problem... until I start adding some more mixins.
The problem I have, is that I can have an after_initialize in any one of my mixins, so I need to include a super call at the start to make sure the other mixin after_initialize calls get called. Which is great, until I end up calling super and I get an error because there is no super to call.
Here's a little example, in case I haven't been confusing enough:
class Iso < ActiveRecord::Base
include Shared::TracksSerialNumberExtension
include Shared::OrderLines
extend Shared::Filtered
include Sendable::Model
validates_presence_of :customer
validates_associated :lines
owned_by :customer
order_lines :despatched # Mixin
tracks_serial_numbers :items # Mixin
sendable :customer # Mixin
attr_accessor :address
def initialize( params = nil )
super
self.created_at ||= Time.now.to_date
end
end
So, if each one of the mixins have an after_initialize call, with a super call, how can I stop that last super call from raising the error? How can I test that the super method exists before I call it?
You can use this:
super if defined?(super)
Here is an example:
class A
end
class B < A
def t
super if defined?(super)
puts "Hi from B"
end
end
B.new.t
Have you tried alias_method_chain? You can basically chained up all your after_initialize calls. It acts like a decorator: each new method adds a new layer of functionality and passes the control onto the "overridden" method to do the rest.
The including class (the thing that inherits from ActiveRecord::Base, which, in this case is Iso) could define its own after_initialize, so any solution other than alias_method_chain (or other aliasing that saves the original) risks overwriting code. #Orion Edwards' solution is the best I can come up with. There are others, but they are far more hackish.
alias_method_chain also has the benefit of creating named versions of the after_initialize method, meaning you can customize the call order in those rare cases that it matters. Otherwise, you're at the mercy of whatever order the including class includes the mixins.
later:
I've posted a question to the ruby-on-rails-core mailing list about creating default empty implementations of all callbacks. The saving process checks for them all anyway, so I don't see why they shouldn't be there. The only downside is creating extra empty stack frames, but that's pretty cheap on every known implementation.
You can just throw a quick conditional in there:
super if respond_to?('super')
and you should be fine - no adding useless methods; nice and clean.
Rather than checking if the super method exists, you can just define it
class ActiveRecord::Base
def after_initialize
end
end
This works in my testing, and shouldn't break any of your existing code, because all your other classes which define it will just be silently overriding this method anyway