I would like to do something like:
class TestController < InheritedResources::Base
def test_method
self.var1 + self.var2
end
private
def test_params
params.require(:test).permit(:var1, :var2)
end
end
Where in the view I could call from the built in controller index:
test.test_method
I've tried adding a create method to the controller as follows:
def create
Test.create!(require(:test).permit(:var1, :var2, :test_method))
end
I've also tried updating the params directly:
private
def test_params
params.require(:test).permit(:var1, :var2, :test_method)
end
I've also tried making a helper method, but I knew that was doomed to fail because it wouldn't have access to var1 and var2.
I guess I just don't understand two things: one how to make my var1 and var2 white-listed so I can use them, and more importantly how to add a method to my model using strong parameters, because attr_accessible doesn't work in my models anymore.
EDIT:
Let me rephrase a little, maybe it will help. I can get access to individual Test objects in my view with a simple call to tests.each |test| in the view. I just want to make methods that act on my already defined active record variables for that object, hence var1 and var2. The problem is when I define a new method in my controller it is private to the object and I won't have access to it with a call from an instance of the object. Better yet, I would like to just be able to define a new variable, local to the object, that is created after it has propagated its other fields from the db.
EDIT2: I'm aware I'm probably missing the design pattern here. It can be hard to describe that I want X, when really I need Z. Thanks for the patience.
Thanks for the help.
There's no reason for white-listing parameters that you'll directly use.
White-listing with strong parameters is useful only when you call function like ActiveRecord#update that simply take every key from the dictionary, so you can control with key you want to allow and which not.
In this case, just do:
class TestController < InheritedResources::Base
def test_method
#result = params[:var1] + params[:var2]
end
end
And in your view, just print the #result variable wherever you want
<%= #result %>
This is the Rails way. You can of course call the variable as you want.
Helper methods are useful only for more complex cases.
Related
I have this function in my user.rb Model :
def change_key
self.public_key = params[:public_key]
end
I want to call this function from a script on Views (new.html.erb) like this :
change_key(x);
and I want self.public_key to get the x value
The result I get is :
undefined local variable or method `params' for
Any thoughts??
To pass an argument, you would do
def change_key(params)
self.public_key = params[:public_key]
end
Which you could call as:
#model.change_key(x)
And, as long as #model has an attribute public_key, and x is something hash-like, then it should work.
Unless, of course, you expect #model.public_key to be persisted. In which case, it would be something more like:
def change_key(params)
self.public_key = params[:public_key]
self.save
end
Which is silly because you could do that in one shot, like:
def change_key(params)
self.update(public_key: params[:public_key])
end
But, why declare a change_key method at all when you can simply do:
#model.update(public_key: params[:public_key])
Which, of course, you definitely do not want to do in a view because (IMO) your views should never be making changes to your models in your views. Doing so requires that your view layer requires intimate knowledge of your model layer - which makes your application brittle.
Your method needs to expect an argument:
def change_key(arg)
self.public_key = arg
end
However since this is a model method, it should not be called directly in the view as this is not MVC. But if you wanted to use it in a controller action, that might work. You'd need to define a controller action and maybe use javascript to make an ajax request to your controller action, where it could then operate on your model.
I've written a little helper method in my ApplicationController like this:
helper_method :dehumanize
def dehumanize (string)
string.parameterize.underscore
end
Now I would like to use it in one of my model files, but it seems not to be available there.
I tried also with:
ApplicationController.dehumanize(title)
in the model but it doesn't work.
any clue on how to make it work there?
thanks,
Models generally can't/don't/shouldn't access methods in controllers (MVC conventions), but the method you've written doesn't necessarily belong in a controller anyway - it would be better as an extension to the string class.
I would suggest you write an initializer to add dehumanize to String:
\config\initializers\string_dehumanize.rb
class String
def dehumanize
self.parameterize.underscore
end
end
You will need to restart your server/console but then you can call .dehumanize on any string:
some model:
def some_method
string1 = 'testing_the_method'
string1.dehumanize
end
Matt's answer is totally right, but to give you some clarification, you want to make sure that you're calling your methods on objects / instances, rather than classes themselves
For example, you mentioned you tried this:
ApplicationController.dehumanize(title)
This will never work because it's calling a method on a class which is not initialized, not to mention the class doesn't have that method. Basically, what will you expect if you called this method?
The way to do it is to use the method Matt recommended, or use a class method on your model itself, which will allow you to call the model's method directly:
#app/models/model.rb
class Model < ActiveRecord::Base
def self.dehumanize string
string.parameterize.underscore
end
end
# -> Model.dehumanize title
I'm reading the Rails AntiPatterns book, which I'm enjoying a lot. At one point, the author talks about the goodness of composition and it gives an example where an Order class gives the responsibility of conversion (to other formats) to another class, called OrderConverter. The classes are defined as:
class Order < ActiveRecord::Base
def converter
OrderConverter.new(self)
end
end
class OrderConverter
attr_reader :order
def initialize(order)
#order = order
end
def to_xml
# ...
end
def to_json
# ...
end
...
end
And then the author says: "In this way, you give the conversion methods their own home, inside a separate and easily testable class. Exporting the PDF version of an order is now just a matter of call-ing the following:"
#order.converter.to_pdf
Regarding to this, my questions are:
Why do you think that order object is preceded by an #? Shouldn't it be created as:
order = Order.new
And then convert by doing:
order.converter.to_pdf
Why is the attr_reader :order line needed in the OrderConverter? It's so we can access the order from an OrderConverter object? Is it needed to be able to do
order.converter.to_pdf ? We could do that without that attr_reader right?
An instance of Order is passed to the initialize method and stored as an instance variable (using the # syntax : #order). This way, this variable can be accessed from other methods in the converter (the variable has the instance scope) :
class OrderConverter
def to_pdf
#order.items.each do |item|
# Write the order items to the PDF
end
end
end
The attr_reader is not strictly required, but is a convenient way to access the Order object from other methods :
class OrderConverter
def to_pdf
order.items.each do |item|
# Write the order items to the PDF
end
end
end
It will also allow you to get the reference to the order out of any converter instance :
converter.order
The # on the front of the variable makes it an instance variable. If it wasn't there the variable would just be a local variable. I'm guessing that since this is a book about Rails, it's assuming that this code would be in a controller. Variables that controllers want to share across methods or expose in their views need to be instance variables. If this is the case #order was probably created either via parameters from a request or with values pulled from the database.
This probably isn't that significant though, both his example and your example work - I think the author was just showing how a call to the OrderConverter would look, and ignored how the Order object got created.
attr_reader :order creates a "getter" method for the #order instance variable in OrderConverter - it's not needed for to_pdf - it would be used to get the Order back out of the OrderConverter via converter.order. I don't see any need to have this in the code you've given so far, but maybe there's some need for it later.
I'm reading a book called RailsAntiPatterns. In the converter method below, a new OrderConverter object is instantiated, and I assume self refers to an instance of Order class.
# app/models/order.rb
class Order < ActiveRecord::Base
def converter
OrderConverter.new(self)
end
end
# app/models/order_converter.rb
class OrderConverter
attr_reader :order
def initialize(order)
#order = order
end
def to_xml # ...
end
def to_json # ...
end
def to_csv # ...
end
def to_pdf # ...
end
end
Why instantiate a new class inside of converter?
Why does self need to be passed as an argument?
Can you summarize in lay terms what's going on?
Why instantiate a new class inside of converter?
Of course, it's up to the choice of the author, but it's probably convenient. For instance:
#my_order = Order.new
#my_order.converter.to_xml
That reads quite nicely, which is important in the eyes of a Rubyist. As the original designer of Ruby, Yukihiro Matsumoto (Matz) has said:
But in fact we need to focus on humans, on how humans care about doing programming or operating the application of the machines. We are the masters. They are the slaves.
Readibility for humans is, therefore, important if you wish to produce elegant Ruby code.
Why does "self" need to be passed as an argument?
Quite simply, OrderConverter requires an order to convert. Since the method converter is defined for instances of the Order class, an instance that wishes to convert itself will pass self as the argument to OrderConverter#new.
can you summarize in lay terms what's going on?
I hope the above has done that for you.
There's not much happening here.
def converter
OrderConverter.new(self)
end
this method creates a new OrderConverter and returns it. OrderConverter is passed a reference to the Order (self) that it can use to do its work (converting).
That's basically it.
he's returning a new instance of OrderConverter whenever you call the instance method "converter" from the Order class (it's an implicit return).
the constructor from OrderConverter takes an instance of Order as its first argument.
regarding the "why" questions, there's no real answer as far as Ruby is concerned, it's up to the implementator -i.e. the author- what the code actually does.
Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.