I have a model that defines methods based off of the entries in another model's table: eg Article and Type. An article habtm types and vice versa.
I define in Article.rb:
Type.all.each do |type|
define_method "#{type.name}?" do
is?(:"#{type.name}")
end
end
This works great! it allows me to ensure that any types in the type db result in the methods associated being created, such as:
article.type?
However, these methods only run when you load the Article model. This introduces certain caveats: for example, in Rails Console, if I create a new Type, its method article.type_name? won't be defined until I reload! everything.
Additionally, the same problem exists in test/rspec: if I create a certain number of types, their associated methods won't exist yet. And in rspec, I don't know how to reload the User model.
Does anyone know a solution here? Perhaps, is there some way to, on creation of a new Type, to reload the Article model's methods? This sounds unlikely.. Any advice or guidance would be great!
I think you'll be better off avoiding reloading the model and changing your api a bit. In Article, are you really opposed to a single point of access through a more generic method?
def type?(type)
return is? type if type.is_a? String # for when type is the Type name already
is? type.name # for when an instance of Type is passed
end
If you're set on having separate methods for each type, perhaps something like this would work in your Type class
after_insert do
block = eval <<-END.gsub(/^ {6}/, '')
Proc.new { is? :#{self.name} }
END
Article.send(:define_method, "#{self.name}?", block)
end
Related
I'm using the Null Object pattern in my Rails application to implement the concept of a guest user account.
Like many apps, I have a method on ApplicationController called current_user.
In the case of a non-logged in user, I want to use my guest user null object.
It works in many cases, but then there run into something like the following -
params.merge({ user: current_user })
MyModel.new(params)
Of course this fails, with the following exception.
ActiveRecord::AssociationTypeMismatch: User expected, got GuestUser
My question is, what is a way to elegantly handle this kind of case. The idea for the Null Object pattern is that you can transparently swap in this null object and have it essentially be a duck type of the real object.
It's obvious how to do that for methods being called on the object, but in this case, I want to be able to pass this in and basically have it set the association column to null, rather than needing a whole bunch of custom logic (avoiding that is the whole point of the null object pattern anyway).
A polymorphic relation isn't quite it.
Quick answer: No such thing as an elegant way to handle that (I'm not sure how elegance is quantified).
You'll have to create a concern that mimics the persistence methods of the model from which your null object is based on (User). You'll also have to write methods to appease ActiveRecord to make the associated column be nil.
Fortunately for you, this use-case has been solved
if your MyModel accepts null for user_id, then you can do
params.merge(user: current_user) unless current_user.is_a?(GuestUser)
MyModel.new(params)
Using the null object pattern here is definatly not a good idea since you need database generated ids to build associations and maintain referential integrity if you intend the user to have any kind of persistence before "registering".
Allowing a MyModel to be created without a user_id would essentially create an orphaned record and just gives you another problem of linking it to user behind the screen. Thats why your schema should not allow it in the first place.
Rather you want to create the guest user record when needed (like when a guest user adds the first item to a cart) and use a recurring task (like a Cron tab) to periodicaly clean out junk records.
I would also consider if you really want to setup guest users as a seperate class since STI and polymorphism tends to get really messy when joining. Just use a timestamp column (records when the account was activated) or an enum instead.
One option would be to override the user= method, so that it's aware of the existence of GuestUser (and can handle appropriately):
def user=(value)
if value.is_a?(GuestUser)
super(nil)
else
super
end
end
All mass-assignment methods in Rails (create, update, etc.) will use the appropriate setter to set the value. This can be easily be put into a concern if this is a common pattern in your application.
If you don't allow nil in the user_id column you have the flexibility to do something like assign a sentinel value, which you could then use in your accessor as well:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end
I had a similar problem. I just went from assigning the object to assigning the object.id which I set to nil on the Null Object. It is kind of a hack I think though.
I wasn't sure how to get it to work syntax wise. I did get it to work like the following though:
scope :out_of_stock, lambda{ |company| get_inventory(company, 'out_of_stock') }
scope :in_stock, lambda{ |company| get_inventory(company, 'in_stock') }
def self.get_inventory(company, stock_status)
StockInventory.find_all_by_....
end
However, is there a way to do it without the anonymous functions?
First of all, there are no static functions in Ruby. The self.get_inventory is a class method, which is in effect a singleton instance method on the Company class. The scope call is itself a class method that dynamically defines other class methods, in this case out_of_stock and in_stock.
Second, it's hard to tell what you're trying to do, since you didn't include your entire method, but I'll assume you're trying to get the instances of StockInventory that have a particular company id and inventory status.
ActiveRecord allows you to use finders, as it appears you've done here, or relations, which are a bit more flexible, as they are typically chainable, and because their execution is delayed until absolutely necessary, meaning you can pass them around to different methods without hitting the database. Unless I'm looking up a single object with a known id, I usually stick to relations for this reason.
I'm going to make a few assumptions, since your question isn't particularly clear. If you have defined a relation between Company and StockInventory (like has_many :stock_inventories and belongs_to :company), you can use that relation as a starting point and defined a method like this on the StockInventory class:
def self.in_stock
where(stock_status: "in stock") # or whatever
end
Note that instead of passing in a Company instance as an argument to a Company class method that ultimately loads some other class (this should be a red flag), you call this directly on the StockInventory class or any StockInventory relation. Plus, since it's a relation, you can chain other ActiveRecord methods to it, i.e. my_company.stock_inventories.in_stock.limit(10).
You'll need to alter this method to fit your particular database columns and statuses, but this is as complicated as it needs to get.
Whatever the case, I recommend you read the Active Record Query Interface guide - there's a lot you can do with it once you understand how it works.
I am trying to use single table inheritance for some of my models. The base model is a Tournament, and I wish to extend this to create different types of tournaments. For instance, I might want to add a SingleEliminationTournament, or a DoubleEliminationTournament, both of which would inherit from Tournament. I have 2 questions, both of them somewhat related.
1) I would like the user to be able to create tournaments with a form, and to do this they would need to select one of the subclasses. Is there a way to get all of the subclasses and use them to populate a select box or something like that?
2) Since this information is going into a form, it would be nice to be able to validate the input into type. To do this, I would like to add a validation in the Tournament class that could check to make sure the Type was valid.
Obviously, I could hard code the values into the validation and the form, but I would not like to do that. Any help would be appreciated. Thanks!
TheModel.subclasses
would give you a list of types you need to include, but only if the models are loaded at runtime. They will always be loaded in production mode. You will have to load them manually in development mode.
You could create a directory with tournaments in them and load them with Dir.glob('app/tournaments/**/*_tournament.rb'). This gives you a nice listing all the tournament files you've specified. Because of convention, you can then infer the proper class name for each tournament.
Store this list of tournament names somewhere for reference in you validations and forms.
I'm not a Rails expert and I'm not sure if this can be considered clean, but for the validation part of your question, this worked for me:
Inside Tournament model:
def validate_type_implemented
klass = type.constantize rescue Object
raise "Given type not available." unless klass.class == Class and klass <= self.class
end
I've just been reading this question which is about giving an ActiveRecord model's date field a default value. The accepted answer shows how to set the default value from within the controller. To my mind, this sort of business logic really belongs in the model itself.
Then I got to thinking how if this were Java I'd probably set the initial field value when declaring the instance variable or within the constructor. Since database-backed fields don't have to be explicitly declared within ActiveRecord models, is this something that you could use the model's initialize method for? I'm curious because I've not really seen much use of constructors for ActiveRecord models within the Rails code that I've looked at. Do they have a role to play and if so, what is it?
I do this quite often actually for default values. It works well and still lets the user change it. Remember, the initialize method is called when you say MyObject.new. However, you may want to read this blog entry (albeit a bit outdated) about using initialize.
You should use after_initialize instead of initialize. The initialize method is required by ActiveRecord::Base to prepare many of the convenience methods. If an after_initialize method is defined in your model it gets called as a callback to new, create, find and any other methods that generate instances of your model.
Ideally you'd want to define it like this:
def after_initialize
#attribute ||= default_value
end
Also note, you cannot use this callback like the others, you must define a method named after_initialize (like above) for it to work. You can't do the following:
after_initialize :run_some_other_method
#TopherFangio's answer is correct. It seems that the ActiveRecord API changed some time between his answer (2009) and now (2015).
As of today (Rails 4 with ActiveRecord 4.2.0), here's how you add initializers according to the ActiveRecord docs:
class Widget < ActiveRecord::Base
after_initialize |new_widget|
new_widget.name ||= 'Unnamed Widget'
end
end
You can verify with puts statements or by inspecting the new object from rails console that it actually initializes correctly.
According to this blog, active record doesn't always use new, so initialize might not be called on your object.
I am using single table inheritance in my rails application, and want to explicitly set the type of an instance.
I have the following;
class Event < ActiveRecord::Base
class SpecialEvent < Event
which is implemented through single table inheritance.
SpecialEvent.new works as expected, but I want to be able to do things like
Event.new(:type => 'SpecialEvent')
So I can create different sub_types easily in the application.
However this doesn't work and seems to set :type to nil, not the value I set it to; I suspect this is because by calling Event.new it is overwriting the :type argument.
Has anyone got a good way of doing this?
If you're trying to dynamically instantiate a subtype, and you have the type as a string, you can do this:
'SpecialEvent'.constantize.new()
from "Pragmatic - Agile Web Development with rails 3rd edition", page 380
There’s also a less obvious constraint (with STI). The attribute type
is also the name of a built-in Ruby method, so accessing it directly
to set or change the type of a row may result in strange Ruby
messages. Instead, access it implicitly by creating objects of the
appropriate class, or access it via the model object’s indexing
interface, using something such as this:
person[:type] = 'Manager'
man, this book really rocks
No, I want to create instances of
sub-types, where I want to
programmatically determine which
sub_type they are
– HermanD
You could use a factory pattern, although I have heard recently that people frown on the overuse of this pattern. Basically, use the factory to create the actual types you want to get
class EventFactory
def EventFactory.create_event(event_type)
event_type.constantize.new()
end
end
To me it sounds like you'll need some mojo in the event#create action:
type = params[:event].delete(:type)
# check that it is an expected value!!!
die unless ['Event', 'SpecialEvent'].include(type)
type.constantize.new(params[:event])
Apparently, Rails does not allow you to set Type directly. Here's what I do...
klass_name = 'Foo'
...
klass = Class.const_get(klass_name)
klass.new # Foo.new
I believe .constantize is a Rails inflector shortcut. const_get is a Ruby method on Class and Module.
Up front I'll agree that STI is often NOT the best way to deal with things. Polymorphism, yes, but it's often better to use a polymorphic association than STI.
That said, I had a system in which STI was important. It was a judicial system and things like court cases were remarkably similar across their types and generally shared all their essential attributes. However, a civil case and a criminal case differed in the elements they managed. This happened at several levels in the system so abstracted my solution.
https://github.com/arvanasse/sti_factory
Long story short, it uses a factory method to leverage the common approach described above. As a result, the controller can remain neutral/ignorant of the particular type of STI class that you're creating.
You can use the Rails safe_constantize method, which will ensure the object/class actually exists.
For example:
def typeify(string)
string.classify.safe_constantize
end
new_special_event = typeify('special_event').new