I have a model called ToolFilter with a column of 'tool_type'. The string here refers to a class for a tool. I put a method in my application_controller called tools_list that gets the descendants of Tool.This works nicely in my frontend, but ToolFilter is complaining about the method tools_list.
class ToolFilter < ActiveRecord::Base
validate :existence_of_tool
def existence_of_tool
unless tools_list.include? tool_type
errors.add(:tool_type, "Invalid tool_type {{tool_type}}, use 'tools_list' to see a list of valid tool_object_types")
end
end
class ApplicationController < ActionController::Base
helper_method :tools_list
def tools_list
Rails.application.eager_load!
Tool.descendants
end
It's a bit strange to tell a model about other classes in the file system, but I need to validate that it is one of these. Should I put tools_list is a module and include it in ToolFilter? Any suggestions?
Write this to include helper in your model
ApplicationController.helpers.tool_list
Though I will not recommend calling helper in model.
And checking tools with classes is damm bad idea.
I ended up creating a module called ToolExtention which has these helper methods in them. I then included this module in my controllers wherever it was needed and moved my logic from the views into the controller which I believe is better practice.
module ToolExtension
def self.tools_list
Rails.application.eager_load!
Tool.descendants
end
...
class ProjectsController < ApplicationController
include ToolExtension
...
ToolExtension.tools_list
Related
We are loading code dynamically with concerns, based on some environment variables, which works pretty nice.
Something like this:
# User class
class User
include DynamicConcern
end
module DynamicConcern
extend ActiveSupport::Concern
included do
if "Custom::#{ENV["CUSTOMER_NAME"].camelize}::#{self.name}Concern".safe_constantize
include "Custom::#{ENV["CUSTOMER_NAME"].camelize}::#{self.name}Concern".constantize
end
end
end
# custom code
module Custom::Custom123::UserConcern
extend ActiveSupport::Concern
included do
...
end
end
We are using this since years and it worked absolutely fine in models. Some days ago we tried to use the same approach with Controllers, but realized that this approach doesn'
t work fine with inheritance, where the parent class inherits the concern as well as the inherited class:
class ApplicationController < ActionController::Base
# this gets loaded and includes the right dynamic module
include DynamicConcern
end
class ShopController < ApplicationController
# this is NOT getting loaded again and skipped,
# since it has been loaded already in the parent controller
include DynamicConcern
end
Is there a way to tell rails that it should include/evaluade the concern a second time, since the second time it would have another class name which would include another module?
I'm not looking for other solutions, since a lot of our code is based on this approach and I think it's possible to solve this without rewriting everything.
Thanks!
You are only trying to dynamically include modules based on the class name.
It's not necessary to make a concern but it can be a normal class, and the include action can be a normal method. Every time you want to call it, just call it like any other method.
Because you have already written your code with ActiveSupport::Concern in an include fashion. I guess the following refactor may work even though I cannot guarantee it. The idea is simple:
Just make it a normal method with the target class as the parameter. You can include it (it automatically calls dynamic_include in included hook).
If the module is already included in the ancestor hierarchy chain, just invoke the dynamic_include will immediately call the method and do the dynamic includes.
Please give it a try and let me know if it works for your scenarios.
module DynamicConcern
extend ActiveSupport::Concern
included do
def self.dynamic_include(klass)
if "Custom::#{ENV["CUSTOMER_NAME"].camelize}::#{klass.name}Concern".safe_constantize
klass.include "Custom::#{ENV["CUSTOMER_NAME"].camelize}::#{klass.name}Concern".constantize
end
end
dynamic_include(self)
end
end
class ApplicationController < ActionController::Base
# this gets loaded and includes the right dynamic module
include DynamicConcern
end
class ShopController < ApplicationController
# this is NOT getting loaded again and skipped,
# since it has been loaded already in the parent controller
dynamic_include(self)
end
Actually it's a feature of Rails that the same module doesn't get loaded multiple times.
We started to use the normal ruby module inclution hooks and it worked fine!
module CustomConcern
def self.included(base)
custom_class_lookup_paths = [
"#{HOSTNAME.camelize}::Models::#{base.name}PrependConcern",
"#{HOSTNAME.camelize}::Controllers::#{base.name}PrependConcern"
].map{|class_string| class_string.safe_constantize }.compact
custom_class_lookup_paths.each do |class_string|
base.send :include, class_string
end
end
I need some help with my plugin. I want to extend ActiveRecord::Base with a method that initializes another method that can be called in the controller.
It will look like this:
class Article < ActiveRecord::Base
robot_catch :title, :text
...
end
My attempt at extending the ActiveRecord::Base class with robot_catch method looks like following. The function will initialize the specified attributes (in this case :title and :text) in a variable and use class_eval to make the robot? function available for the user to call it in the controller:
module Plugin
module Base
extend ActiveSupport::Concern
module ClassMethods
def robot_catch(*attr)
##robot_params = attr
self.class_eval do
def robot?(params_hash)
# Input is the params hash, and this function
# will check if the some hashed attributes in this hash
# correspond to the attribute values as expected,
# and return true or false.
end
end
end
end
end
end
ActiveRecord::Base.send :include, Plugin::Base
So, in the controller, this could be done:
class ArticlesController < ApplicationController
...
def create
#article = Article.new(params[:article])
if #article.robot? params
# Do not save this in database, but render
# the page as if it would have succeeded
...
end
end
end
My question is whether if I am right that robot_catch is class method. This function is to be called inside a model, as shown above. I wonder if I am extending the ActiveRecord::Base the right way. The robot? function is an instance method without any doubt.
I am using Rails 3.2.22 and I installed this plugin as a gem in another project where I want to use this functionality.
Right now, it only works if I specifically require the gem in the model. However, I want it the functionality to be included as a part of ActiveRecord::Base without requiring it, otherwise I'd have to require it in every model I want to use it, not particularly DRY. Shouldn't the gem be automatically loaded into the project on Rails start-up?
EDIT: Maybe callbacks (http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html) would be a solution to this problem, but I do not know how to use it. It seems a bit obscure.
First, I would suggest you make sure that none of the many many built in Rails validators meet your needs.
Then if that's the case, what you actually want is a custom validator.
Building a custom validator is not as simple as it might seem, the basic class you'll build will have this structure:
class SpecialValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# Fill this with your validation logic
# Add to record.errors if validation fails
end
end
Then in your model:
class Article < ActiveRecord::Base
validates :title, :text, special: true
end
I would strongly suggest making sure what you want is not already built, chances are it is. Then use resources like this or ruby guides resources to continue going down the custom validator route.
Answer
I found out the solution myself. Bundler will not autoload dependencies from a gemspec that my project uses, so I had to require all third party gems in an engine.rb file in the lib/ directory of my app in order to load the gems. Now everything is working as it should.
Second: the robot_catch method is a class method.
I am new to Rails and have written a method to_csv which I have put it in products_controller.rb, but I want it to available to all other controllers too. What is the preferred way to do that? Is it in application.rb?
Similarly, if I am writing a method in some model.rb, how to share that method between all the models?
application_controller will be the place. If for model, maybe you can write in a module, then
include in your model which you want to use.
1) Try ActiveRecord::Base monkeypatching.
The initializer directory is a best place to collect all those little task
So, try /config/initializers/active_record_extension.rb,
class ActiveRecord::Base
def self.export(parameters)
#your csv logic goes here
end
end
or
2) create master class, which is used to inherit by all active_record model
for example /models/your_class.rb
class YourClass < ActiveRecord::Base
def self.export(parameters)
#your csv logic goes here
end
end
class CsvDB < YourClass
end
You can also create a separate model without inheriting from ActiveRecord::Base and define your csv method in that particular model. And from any controller just call
model_name.method_name(parameters)
For example, in the model CsvDB:
class CsvDB
def export(parameters)
# your csv logic goes here
end
end
From any controller just call
CsvDB.export(parameters)
I'm slightly new to Rails (i.e. stupid and need some teachin').
I have a controller (call it ControllerFoo) that performs a particular task (theMethod) which could be useful in other controllers (say, from within ControllerBar). So, of course, the method is defined as self.theMethod in ControllerFoo (which means it's a class method, right?), and access in ControllerBar as ControllerFoo.theMethod. Confused yet?
Here's the problem: the ControllerFoo.theMethod uses session data, and when called from ControllerBar, session is nil. In fact, it seems that session is also nil when being called from itself. I guess what I'm saying is class methods can't access session data?
<rant>I hate how session data can't simply be accessed anywhere like in PHP</rant>
So for now, since I'm not smart enough to know how to do this correctly, I've just duplicated the logic in several places throughout my app. But this is not DRY at all, and I hate it.
So how can I create a method in a controller that's accessible to other controllers and can also access session data?
class ControllerFoo < ApplicationController
def self.theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerBar < ApplicationController
def show
ControllerFoo.theMethod("Hello,")
end
end
Couple of options...
Put the shared method in the shared parent ApplicationController
Create a module that both ControllerFoo and ControllerBar will include
e.g.
module SharedModule
def theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerFoo < ApplicationController
include SharedModule
end
class ControllerBar < ApplicationController
include SharedModule
def show
theMethod("Hello,")
end
end
The way you would do this is Ruby would be to create a module containing the class (or instance) methods you wish to share and include it in the classes you need to have those methods defined in.
If a few of my models have a privacy column, is there a way I can write one method shared by all the models, lets call it is_public?
so, I'd like to be able to do object_var.is_public?
One possible way is to put shared methods in a module like this (RAILS_ROOT/lib/shared_methods.rb)
module SharedMethods
def is_public?
# your code
end
end
Then you need to include this module in every model that should have these methods (i.e. app/models/your_model.rb)
class YourModel < ActiveRecord::Base
include SharedMethods
end
UPDATE:
In Rails 4 there is a new way to do this. You should place shared Code like this in app/models/concerns instead of lib
Also you can add class methods and execute code on inclusion like this
module SharedMethods
extend ActiveSupport::Concern
included do
scope :public, -> { where(…) }
end
def is_public?
# your code
end
module ClassMethods
def find_all_public
where #some condition
end
end
end
You can also do this by inheriting the models from a common ancestor which includes the shared methods.
class BaseModel < ActiveRecord::Base
def is_public?
# blah blah
end
end
class ChildModel < BaseModel
end
In practice, jigfox's approach often works out better, so don't feel obligated to use inheritance merely out of love for OOP theory :)