How can I make rails helpers more object-oriented - ruby-on-rails

I'd like to, for all helpers, across my entire Rails application, replace this syntax:
time_ago_in_words(#from_time)
with this:
#from_time.time_ago_in_words
Ideally this change would also make the helpers available anywhere in my application (the same way, say, 5.times is).
Does anyone know of a plugin that does this? Would it be difficult to roll my own?

If you create a new model you can make it inherit the methods from an existing class.
Example (app/models/mykindofstring.rb):
class Mykindofstring < String
def all_caps_and_reverse
self.upcase.reverse
end
end
Then from the controller:
#my_string = Mykindofstring.new('This is my string')
and finally calling it from the view:
<%= #my_string %>
displays as This is my string, while:
<%= #my_string.all_caps_and_reverse %>
displays as GNIRTS YM SI SIHT.
The class inherits from the string class, so all the methods that apply to a string also apply to a mykindofstring object. The method in it is just a new name for a combination of two existing string methods, but you can make it do whatever you want, such as a method to convert a time into a formatted string.
Basically it's just extending a current Ruby class with your own methods instead of using functions/helpers.
Major Edit
Based on Ians comment to this answer and his own answer to the above question, I played around a bit in Rails and came up with adding this to the application_helper.rb:
module ApplicationHelper
String.class_eval do
def all_caps_and_reverse
self.upcase.reverse
end
end
end
Now just call the method on any string in the app:
#string = 'This is a string.'
#string.all_caps_and_reverse
.gnirts a si sihT

You won't find any easy way to do this across your whole application for all (or even most) helpers.
Helpers are structured as modules meant to be included in view rendering classes. To leverage them, you'd have to keep a copy of an ActionView around (not a HUGE deal I suppose).
You can always open up the classes and specify each helper you want, though:
class Time
def time_ago_in_words
ActionView::Base.new.time_ago_in_words self
end
end
>> t = Time.now
=> Tue May 12 10:54:07 -0400 2009
>> t.time_ago_in_words
=> "less than a minute"
(wait a minute)
>> t.time_ago_in_words
=> "1 minute"
If you take that approach, I recommend caching the instance of ActionView::Base that you use. Or, if you don't want to go so heavyweight, you can create your own class to just include the helpers you want (you don't want to include the helpers directly in each Time, Date, String, etc, class, though, as that will clutter up the method namespace pretty fierce -- and conflict with the natural names you want).

If you are using Ruby 2.1 you can use Refinements. They are an alternative to monkey-patching Ruby's classes. to borrow #Jarrod's monkey-patch example:
module MyString
refine String do
def all_caps_and_reverse
upcase.reverse
end
end
end
You can now use this in any object you want:
class Post
using MyString
end
#post.title.all_caps_and_reverse
You can read more about Refinements here.

Make a model, TimeAgo or something similar. Implement following this idea here:
a code snippet for "wordifying" numbers
Then, in your controller, create the instance variables using this class. Then, in your view, call #from_time.in_words

Related

RIght way of writing module methods in Ruby

what is right way of writing module? is it only used to stock some peace of code to minimize the number of lines, or is it something much more important than that
I have used and seen ways of writing module, I am working on setting up correct way to define and standardised module. this example is kind of controller code that we use in rails
Way 1 :-
module B
extend ActiveSupport::Concern
def process_items
# do somthing...
#items.pluck(:names)
end
end
Class A
include B
def index
#items = Item.all
#item_names = process_items
end
end
Way 2 :-
module B
extend ActiveSupport::Concern
def process_items(items)
# do somthing...
items.pluck(:names)
end
end
Class A
include B
def index
#items = Item.all
#item_names = process_items(#items)
end
end
Way 1 :-
When I see this independently, its not much readable as I don't know how #items appeared in this method
Unit testing would be hard for method as its dependent
Way 2 :-
Looking at method I can see input is coming we are processing it and returning it back (readablity is good)
Unit testing is easy to this, we wll call method pass what it needs and expect
The way I see modules should be independent, self explanatory, it should be generic so that can be used in any class, kind of helpers. But other way could be dependent on where we use modules
We are using modules like in rails
We use conccern in models, when we call module method we can use self.<field> we don't need to pass anything because instance variable is supposed to be accesssable in every instance method
View helpers are modules I see they put logic into it hard to understand how the variable come from may be instance variable or params, what about making it method which accept somthing and return it back
Concerns on controllers, like the example I have given
I would like to have thoughts on this, what is best approach out of it? is it something which can be standarise or it is more situational or I don't know yet :)
Note: -
I was looking at this question but answer given on this question is no more valid as referenced links are not working.
Right Way to Use Module
The difference here is practically academic, as if you have attr_reader :x then both #x and x will have the same meaning.
It's understood that within a mixin module you will be referencing methods and/or variables that are part of the class or module doing the "mixing in". As such, seeing #x, or in your case, #items, should not come as a real surprise.
If you want to add it as an explicit argument you're sort of missing a lot of the benefits of using a mixin in the first place. You don't need to mix it in at all, you can just use it like B.process_items(...). In other words, your second approach is having an identity crisis. Is it a stand-alone module that includes Concern for no reason, or a weak mixin?
When it comes to testing, you must test the mixin in a module or class which implements the required features. In this case you need either an #items variable, or an items method, and that must have a value of the expected type.
This should be documented somewhere for clarity, but is effectively an implicit contract with anyone using this module.

Check if a ruby Class is in a particular Module

I have a controller object with controller.class == Admin::TeamsController. I might also have a circumstance like controller.class == Admin::UsersController. Now I want to check if this is true:
controller.class.to_s.match?('Admin::')
I.e., I want to know: Is this object of a class that's defined within the Admin module namespace? To spell that out, is the structure like the following?
module Admin
module SomeOtherModulePerhaps
class TeamsController
end
end
end
My question: Is there a nicer Ruby way to test for this? It feels kind of hacky to convert the class to a string, then do a regex match like that.
EDIT:
For my constrained use case, I could check like this:
controller.class.to_s.split('::').first == 'Admin'
But that doesn't quite solve the general case that other people might have. For example, there might be cases like XyzAdmin::TeamsController that one might want to exclude, on which my first solution fails, or Foo::Admin::TeamsController that one might want to include, on which my second solution fails.
I'd like to find a better way.
Rails comes with module_parents:
module Admin
module SomeOtherModulePerhaps
class TeamsController
end
end
end
controller = Admin::SomeOtherModulePerhaps::TeamsController.new
controller.class.module_parents
#=> [Admin::SomeOtherModulePerhaps, Admin, Object]
controller.class.module_parents.include?(Admin)
#=> true
Under the hood, it uses Module#name, i.e. "Admin::SomeOtherModulePerhaps::TeamsController".
How about
controller.class.const_defined?(:Admin)
returns true or false
What about to use controller_path
https://api.rubyonrails.org/classes/AbstractController/Base.html#method-c-controller_path
controller_path.match?('admin')
You might try playing with Module#nesting, but it’d return rather unexpected results depending on whether the class was defined using fully qualified name or a set of nesting statements.
After all, class names in ruby are simple constants, and one might define the class name in many ways, like:
module A
def self.class!
Class.new do |c|
define_method :test do puts c.name end
end
end
end
A.const_set :C, A.class!
#⇒ A::C
A::C.new.test
#⇒ A::C
Which roughly means, there are tons of ways to fool the best detection mechanism. That said, I’d go with the easiest one.
controller.class.to_s.split('::')[0...-1].include?('Admin')
Any occurrence of Admin would be counted, save for when Admin is the last item in the class name chain.
I want to know: Is this object of a class that's defined within the Admin module namespace?
[...]
Is there a nicer Ruby way to test for this?
Classes aren't defined in modules, therefore, there is neither a nice way nor any other way to test for it.
When you write a class definition body inside a module definition body, you do not create any relationship whatsoever between the module and the class. The only relationship is between the constant that the class gets assigned to and the module, not the class.
Therefore, since this relationship does not exist, you cannot test for it.

Ruby error - Undefined Method

I am try to write a function that will find the items in an array which match the string passed to the function. See code below.
class Island
def filter(string)
for element in self
if element.include? (string)
yield(element)
end
end
end
end
list = ["sasha","rory","rob","anthony","andre","tariq","kimberly","antoinette"]
list.filter("an"){|i| puts i}</i>
How i keep getting "undefined method 'filer' for #
I'm not sure what i'm doing wrong.
First let me object against the solution posted by #Sravan :
While it is true - and sometimes even a good solution - to monkey-patch a class, you have to be careful how to do it, because it may become a time bomb:
Ruby evolves, and new versions often add methods to existing classes. This means that if you add a method Array#search, and a new version of Ruby will also add a method of the same name, your new method will SILENTLY override the one in Ruby. You likely won't notice it for long time, until you are using a feature which is supposed to use Rubys Array#search - maybe by using something new in stdlib - and you get weird results. To track down this type of error can be a nightmare. This is exactly the case when you use search as a method name.
Now, how to do it then? Three possibilities:
(1) If you monkey-patch, use at least a method name which is unlikely to become part of the official interface. It might have your project's name as a prefix, or plenty of underscore characters, and so on. Note that this is not 100% foolproof: A later version of Ruby might add under the hood a private method with exactly the same name than the one you were choosing, but of course the odder your name, the less likely this will happen.
(2) If you don't like this idea of using "clumsy" names, you could at least test before defining the new method, whether it already exists, and throw an exception if it doesn't:
class Array
if self.method_defined?(:search)
raise "#{self.class}::search already defined"
else
def search(...)
...
end
end
end
(3) The third possibility is to avoid monkey-patching and keep the method in your Island class. In this case, the method definition would be different:
class Island
def self.filter(array, string)
...
end
end
and it would be called by
Island.filter(myarray, mystring)
UPDATE: Forgot a forth possibility:
(4) You can make Island a subclass of Array. I don't know what else you want to do with your islands, but maybe this is an option worth considering:
class Island < Array
def filter(string)
...
end
end
Of course, when invoking filter, you need to turn your array into an island, before you can use it:
list = Island.new([....])
Following ruby's convention over configuration, you can add/overwrite any method in any class
So, adding a function to array class makes it accessible to all the arrays. So, in this solution.
1) First thing is you have taken the filter function in Island class, instead, you need to take inside Array class since the list is an array.
class Array
def filter(string)
for element in self
if element.include? (string)
yield(element)
end
end
end
end
list = ["sasha","rory","rob","anthony","andre","tariq","kimberly","antoinette"]
list.filter("an"){|i| puts i}
O/P:
anthony
andre
antoinette
2) Since Filter is a keyword as suggested by other answer, take another name for it. Eg: search
class Array
def search(string)
for element in self
if element.include? (string)
yield(element)
end
end
end
end
list.search("an"){|i| puts i}

Ruby on Rails naming convention in view

In the RoR view You often need to display lists of some models. If You use instance variables for this, like #people and countries, RubyMine IDE will warn You that You should use at most two instance variables in the view.
So let's say You will wrap those in a hash, like:
view_params = { people: #people, countries: #countries }
I know naming is very important in RoR. Is there any naming convention for such hash, which I called view_params (in Django they call it context I guess), or good practice how to deal with this problem?
It's a stupid rule. Turn off the rule in your IDE.
Stick to the better rule - "Methods can be no longer than five lines of code." If you find your controller methods becoming longer than five lines because you're building too many instance variable - then package them up. But if you can create all the instance variables you want in about 5 lines of code, then that's fine.
If you look at the example in Sandi Metz Rules For Developers, all they've done is package the instance variables into another object. Is there really a good reason why having two instance variables associated with a controller instance is worse than making them belong to a new object instance?
For me:
def index
#foo = Foo.get_some
#bar = Bar.get_some
end
Is cleaner and simpler than:
class FooBar
attr_reader :foo, :bar
def initialize(foo, bar)
#foo = foo
#bar = bar
end
end
class FooBarController
def index
foo = Foo.get_some
bar = Bar.get_some
#foo_bar = FooBar.new(foo, bar)
end
end
Use a non-active record ruby class instead of a hash. One instance variable is probably best. See facade pattern: https://robots.thoughtbot.com/sandi-metz-rules-for-developers

set constant values for cuisine like Chinese,Indian in ruby on rails

I want to use Cuisines like (Chinese, Indian, US) as constant values in my application which are defined in a config file. How can I set as constants and how can access in controllers?
This is explicitly not an answer to your question, but a suggestion that you look for alternatives. I think you would be far better off creating a database table with your cuisine names in it than to use constants. Leverage rails associations so that you can write nice readable code.
The problem with using constants is that under many circumstances, they aren't really constant. What happens if you want to add Japanese? What happens if you want to add Thai, but then 6 months later decide to drop it? What happens if you decide that Indian is too broad, and you want "Northern Indian" and "Southern Indian"?
With a database table, you can ensure that the class that are associated with those constants are always in a consistent state. When you need to get them all, they are just a line of code away with
my_cuisines = Cuisine.all
with nice built in iterators.
You can use gem 'settingslogic'
model settings.rb:
class Settings < Settingslogic
source "#{Rails.root}/config/settings.yml"
namespace Rails.env
end
then, use in controller:
Settings.cousines
First, consider what Marc Talbot said. Make sure that you really don't want a normal database model. If you're sure you want to use constants then continue on:
My preferred way to do this is with a pseudo-model.
In app/models/cuisine.rb
class Cuisine
# Should come before the constant declarations
def initialize(name)
#name = name
end
Mexican = new('Mexican')
Chinese = new('Chinese')
Indian = new('Indian')
def to_s
name
end
# other related methods
# like translations, descriptions, etc.
end
Then in the everywhere else in the app you can just reference Cuisine::Mexican or Cuisine::Indian
Also depending on how you are using it you might need a list of the cuisines.
class Cuisine
...
def self.all
[Mexican, Indian, Chinese, ...]
end
end
This technique keeps the code organized and keeps you from writing yet another initializer file.

Resources