I have an action in a Rails application in which I am taking in a "type" for a polymorphic association and looking up a class (DokiCore is the name of my engine):
def complete
model_class = DokiCore.const_get(complete_params[:pointer_type])
pointer = model_class.find(complete_params[:pointer_id])
end
def complete_params
params.require(:progress).permit(
:pointer_id,
:pointer_type
)
end
I'm concerned that a user could supply a random class with "pointer_type" and try to look up other classes in the engine namespace. Is this a worry at all? Is there a Rails-y type way of looking up models only by name or should I add a method that tests the value of pointer_type for all models I want to allow in the polymorphic association?
Yes, something like DokiCore.const_get('::User') will return top-most User class even if there is DokiCore::User.
Just add some white-list checking against your user-param and you'll be fine:
raise 'Invalid class!' unless ['GoodClass'].include? complete_params[:pointer_type]
Related
Does a helper class in Rails 5 (for example UserHelper) have access to the model type it is related to (User)?
I have some shared logic AddFilter that requires the model type to work propery. I now enforce a method filter_for to specify which model is used:
module AddFilter
def filter_for
raise "filter_for not implemented"
end
#...other code
end
Currently I include that logic in many of my helpers:
module UserHelper
include AddFilter
def filter_for
User
end
end
It is possible to retrieve the model type in the AddFilter directly?
Apparently since helpers are included in controllers, a feature from controllers can be used:
# add_filter.rb
def filter_for
controller_path.classify.constantize
end
This retrieves the controller path, classifies the name, then tries to find the constant specified.
Still feels rather hacky, better solutions appreciated!
I've got STI like this:
class Post
end
class Post::Confirmed < Post
end
class Post::Draft < Post
def confirm!
becomes Post::Confirmed
end
end
...# somewhere in controller
# POST /posts/1/confirm
# POST /posts/1/confirm.json
def confirm
#post = Post::Draft.first
#post = #post.confirm! # this is the only way I can reload #post with Post::Confrmed
end
Is it somehow possible to make:
#post.confirm! # I want this #post(Post::Draft) to become Post::Confirmed without reassigning
Or is it just nor RoR way?
Thanks in advance!
The pattern I've found that works best here is having a datetime type field that records when the record was flagged.
For example:
def confirm!
self.confirmed_at = DateTime.now
self.save!
end
Then you can tell when something was confirmed. This comes in especially handy for when you have a situation where something will be flagged but isn't yet, such as setting a publishing date in the future.
Although it might seem a little annoying to not have your STI bag of tricks available, STI is not always the appropriate tool. Generally STI is to differentiate between similar but different models that have a lot of commonality or are used in a common context. It's not supposed to be used to handle different states of a singular model.
What you want in that case is a state-machine type pattern.
I'm doing a bit of metaprogramming in Ruby. I'm writing a library to meta-define some methods for me, specifically in the controller (automate some find_by methods that I have to write for my applications).
Currently I generate these methods by having to pass the name of the model for a particular controller into my meta-programming method. Is there a method in a controller that is tied to an ActiveRecord model.
So, here is a poor example
module AwesomeGem
module ClassMethods
def write_some_methods_for(model)
raise "Class #{model.class} does not inherit ActiveRecord::Base" unless model < ActiveRecord::Base
define_method "money_remaining" do |id=nil|
moolah = id ? model.find(id).money : model.find(params[:id]).money
render text: moolah
end
define_method "money_remaining_poller" do |id=nil|
obj = id ? model.find(id) : model.find(params[:id])
# composes some ajax
render js: moneyjs
moneyjs
end
end
end
end
So, to use this method, I plan to
GamblerController < ApplicationController
write_some_methods_for Gambler
end
Again, how could I make it so I don't have to pass the Gambler class to my method? Is there some sort of method or attribute that I could just call the model directly? eg. self.send(:model)
A simple question with a complex explanation.
Controllers are not tied to a particular model by default. You can have a controller playing with several different models, or even a controller using no model at all.
If you still want your code to work automatically in "classic" cases, you could look at the controller's name, and look for a model with the same name (following rails naming conventions).
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
In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master