simple ruby method helper - ruby-on-rails

I am just learning ruby and wanting to practice writing little helper methods for rails as a good way of revising the basics.
all I would like to do is provide a counter for scoped objects.
so in my view i write this
=stats_counter_for(with_pending_state)
'pending_state' being a particular scope of the model.
def stats_counter_for(object_state)
Photo.object_state.count
end
so i want to pass this through to provide a count of all items with a pending state.
so eventually I can do
=stats_counter_for(with_active_state)
=stats_counter_for(with_inactive_state)
(the equals is from the haml view)
update error message
undefined local variable or method `with_pending_state' for #<#<Class:0x007fbce1118230>:0x007fbce1123770>
=link_to '/ Pending Approval', pending_admin_entries_path
=stats_counter_for(with_pending_state)
=link_to '/ Rejected', rejected_admin_entries_path
Where am I going wrong here? I am sure this is incredibly simple.

You can use the send method:
def stats_counter_for(state)
Photo.send("with_#{state}_state").count
end
So in your views you can use it like that:
= stats_counter_for(:active) # or as a string 'active'
= stats_counter_for(:inactive)

Related

Is it possible to pass a nested property of a hash to function in ruby

I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.

Ruby on Rails: proper way to return data from methods

How do I follow OOP standards within RoR controllers?
The setup: submitting data to a form & then manipulating it for display. This is a simplified example.
app/controllers/discounts_controller.rb
...
def show
#discount = Discount.find(params[:id])
formatted = calc_discounts(#discount)
end
...
private
calc_discounts
half_off = #discount.orig_price * .5
quarter_off = #discount.orig_price * .25
return {:half => half_off, :quarter => quarter_off}
end
...
Or is it better to place this in a library with attr_accessor and then create new instances of the library class within the controller? Or is there an even better way of accomplishing this?
The question to ask yourself is "is this logic useful for the view, model, or both?"
If the answer is that it's only useful for display purposes, I would put that logic in a view helper. If it's also beneficial to the model, put it there. Maybe something like this:
class Discount
def options
{half: (self.orig_price * .5), quarter: (self.orig_price * .25)}
end
end
Then in your controller you can just locate the record in question:
def show
#discount = Discount.find(params[:id])
end
And display it in the view:
<h1>half: <%= #discount.options[:half] %> </h1>
<h1>quarter: <%= #discount.options[:quarter] %> </h1>
Well, you can can add half_off and quarter_off as methods to your model:
class Discount < ActiveRecord::Base
def half_off
orig_price * 0.5
end
def quarter_off
orig_price * 0.25
end
end
.. and then do the following:
def show
#discount = Discount.find(params[:id])
end
Now you can call #discount.half_off and #discount.quarter_off in your view..
First off, you've got some syntax issues there. When you define methods you need to use a def keyword, and since Ruby 1.9 you can use a shortcut when defining hashes that avoids hashrockets, so it's:
def calc_discounts
half_off = #discount.orig_price * .5
quarter_off = #discount.orig_price * .25
return {half: half_off, quarter: quarter_off}
end
Also, you defined a local variable formatter inside of your controller's show method. This doesn't actually do anything but assign some values to a variable that only exists within that method. Only the controller's instance variables (variables with an #) can be passed to the view.
That being said, the best practice in RoR is to keep controllers "skinny", which means only using controllers to authenticate, authorize, load a model, assign an instance variable for you view, handle errors with any of the former, and then render the view according to the format requested.
It's another best practice not to include much logic in your views. This way, your logic can be shared with and reused by other views instead of having to be re-written for each new view you make. It also makes your views more readable, as they will read like simple lists of what is to be shown instead of making people try to decipher embedded ruby all over the place.
If the code is something that one of your other models could benefit from being able to use, put it inside your model code (or make a new plain old Ruby object if the logic is complex or not really cohesive with the existing model).
If the logic is something that is just for making a view prettier or in a better format, but won't actually be used by the models, then it should go in some type of view helper or decorator.

Ruby on Rails: caching data in an object

I've come up with an issue I can't figure out how to solve. I'm new to both Ruby and Rails, and sure there is a simple way to achieve what I'm looking for.
This is the ERB of the show view, showing two equal lines:
<p><%= #user.foo %></p>
<p><%= #user.foo %></p>
Imagine that foo is an intense computational method so I want to cache the result of the first call in order to use it in the second line without having to call foo again. The simplest option would be defining a variable and cache it:
<% foo_cache = #user.foo %>
<p><%= foo_cache %></p>
<p><%= foo_cache %></p>
But I don't want to clutter the "global" scope. A nicer way would be that foo itself could save a cache of the value it returns when it's called the first time:
def foo
return self.cached_foo if self.cached_foo #WARNING: pseudocode here!
#Not cached. Do stuff
...
self.cached_foo = computed_value
computed_value
end
My question is if it's possible to attach data to an object instance dynamically without interfering with the model behind (i.e. without making save and company functions deal with this attached data). Or maybe is there another better way to achieve what I'm looking for?
Thanks.
This is called memoization and it's a common idiom in ruby. It is usually expressed like this:
def foo
#cached_foo ||= begin
# do your heavy stuff here
end
end
#cached_foo should not interfere with ActiveRecord (like make it try to save cached_foo to the database).
What you are looking for is called memoization
def foo
#foo ||= calculate_foo
end
def calculate_foo
# heavy stuff
end
This works thanks to conditional assignment (the||=)
It's an extensive topic so I'll leave you a couple of links about it:
http://rails-bestpractices.com/posts/59-use-memoization
http://gavinmiller.io/2013/basics-of-ruby-memoization/
Plus advanced memoization in case you need to do more complicated stuff such as parameters, storing nil values
http://gavinmiller.io/2013/advanced-memoization-in-ruby/
In fact Active Support had memoizable but it was deprecated and then extracted into a gem
In case you want to use it check it out on:
https://github.com/matthewrudy/memoist
This should do it. And don't be afraid, the instance variable has no impact on the persistence layer.
def foo
#foo ||= compute_foo
end
private
def compute_foo
# ...
end

extract string of method names from database?

I am trying something new and I'm stubborn, so I want to exhaust all possibilities.
I have a model called navbar with a field for links.
Inside the links field, I have stored a number of words:
profile_link community_link
The words are significant, in that, they are also the names of methods I have recorded in navbars_helper:
module NavbarsHelper
def profile_link
link_to current_user do
image_tag(current_user.image.img.mini_avatar)
current_user.name
end
end
def community_link
link_to 'Community', topics_path
end
...
end
The new thing I was trying, was to extract the words from the string and use them to call the methods in my header layout:
- if signed_in?
- #current_group.navbars do |navbar|
- if navbar.kind == "Header"
= navbar.links.to_s
navbar belongs_to group
So, what I get is the string in the header: profile_link community_link
But, what I want is a call to the methods. Is this possible? If so, can you tell me how you would do it?
I'm not very experienced working with arrays and I think it may have something to do with
I guess that it could be made working, although I am not really sure if it is worth: you are adding a lot of complexity for almost no additional benefit.
Said that, something along the lines of
- if signed_in?
- #current_group.navbars.each do |navbar|
- if navbar.kind == "Header"
- navbar.links.split(' ').each do |method|
= self.send(method)
Basically, take the string "profile_link community_link", split it using the space character and then send each method to the view (which is self in this context). Using send just executes the method as you would do normally, but gives you the benefit of deciding which method to execute at runtime :)

Is there a method I can use across controllers and if so, how do I use it?

I have several controllers that take an instance of different classes each (Email, Call, Letter, etc) and they all have to go through this same substitution:
#email.message.gsub!("{FirstName}", #contact.first_name)
#email.message.gsub!("{Company}", #contact.company_name)
#email.message.gsub!("{Colleagues}", #colleagues.to_sentence)
#email.message.gsub!("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d"))
#email.message.gsub!("{ContactTitle}", #contact.title )
So, for example, #call.message for Call, #letter.message for Letter, etcetera.
This isn't very dry. I tried the following:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
def message_sub(asset, contact, colleagues)
asset.message.gsub!("{FirstName}", contact.first_name)
asset.message.gsub!("{Company}", contact.company_name)
asset.message.gsub!("{Colleagues}", colleagues.to_sentence)
asset.message.gsub!("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d"))
asset.message.gsub!("{ContactTitle}", contact.title )
end
end
So in, say, the Letter Controller have this:
#letter = Letter.find(params[:letter]) #:letter was passed as a hash of the letter instance
message_sub(#letter, #contact, #colleagues)
#contact_letter.body = #letter.body
But the above doesn't work.
To answer your question directly, you want a module. You can put it in your lib directory.
module MessageSubstitution
def message_sub(asset, contact, colleagues)
asset.message.gsub!("{FirstName}", contact.first_name)
asset.message.gsub!("{Company}", contact.company_name)
asset.message.gsub!("{Colleagues}", colleagues.to_sentence)
asset.message.gsub!("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d"))
asset.message.gsub!("{ContactTitle}", contact.title )
end
end
Then in your controller
class MyController < ApplicationController
include MessageSubstitution
def action
...
message_sub(#letter, #contact, #colleagues)
end
end
However, as some have already pointed out, this is probably better at the model level:
module MessageSubstitution
def substituted_message(contact, colleagues)
message.gsub("{FirstName}", contact.first_name).
gsub("{Company}", contact.company_name).
gsub("{Colleagues}", colleagues.to_sentence).
gsub("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d")).
gsub("{ContactTitle}", contact.title )
end
end
Which you would then include at the model level, and then call in your controller like #letter.substituted_message(#contact, #colleagues)
However, I'm a little confused that what you posted didn't work, though. If it's defined on ApplicationController it should work. Module-based solution still better IMO.
I think we are missing information needed to fix this problem. Check out section 3 of this guide: Debugging Rails Applications
Step through your code and check the variables at each point to see if they are what you expect.
When you have time, be sure to read the whole guide. It provides an invaluable set of tools for Rails programming.
As a side note, the code you have provided feels like it is more suited to be in the model than in the controller. Your models may be a good candidate for Single Table Inheritance or your models are so much alike that you can condense them into a more generalized model that covers what you need. If either of these options are a good fit, you could move message_sub out of the controller and into the model.
The problem may lie in the fact that gsub! works on the instance of the String object returned by ActiveRecord. To notify ActiveRecord of an attribute change, you should call the method message=(value). So, your method would be like this:
def message_sub(asset, contact, colleagues)
asset.message = asset.message.
gsub("{FirstName}", contact.first_name).
gsub("{Company}", contact.company_name).
gsub("{Colleagues}", colleagues.to_sentence).
gsub("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d")).
gsub("{ContactTitle}", contact.title )
end
(I took some artistic license to chain the gsub method, but it's all in your tastes)
It's not clear if the question is about reusing a method across the controller (as the title suggests) or the best way to do message templates.
To reuse the method across the controller , I guess you already answer the question yourself by using a common class. However as Awgy suggested this bit could better be in the Model than in the Controller.
To template message you can use ERB rather gsub or just 'eval' your string
(if you use #{} instead of {} in your message)
firstName=contact.first_name
company = company_name
...
asset.message = eval "\"#{asset.message}\""
(You might even be able to pass directly contact as a 'bindings' to the eval function.
Personally, I wouldn't delegate this to the controller, isn't this something that the Presenter Pattern is for?

Resources