My show action in a chair controller creates an acronym from a Chair's name. The following codes works:
def show
#chair = Chair.find(params[:id])
#user = #chair.user
first_letters = []
#chair.name.split.each do |word|
first_letters << word[0]
end
#names = first_letters.join
end
I want to extract an acronym method that takes care of creating the acronym. I tried this:
def show
#chair = Chair.find(params[:id])
#user = #chair.user
#names = #chair.acronym
end
def acronym
first_letters = []
#chair.name.split.each do |word|
first_letters << word[0]
end
first_letters.join
end
However, acronym couldn't be accessed from show. The method names and the views need to match, but is there a way to have a separate method acronym like this?
In order for #chair.acronym to work you need to define the acronym method on the Chair model.
Alternatively, you can also refactor like this, keeping the acronym method in your controller:
def acronym(chair)
first_letters = []
chair.name.split.each do |word|
first_letters << word[0]
end
first_letters.join
end
You would call this method like this:
#names = acronym(#chair)
There are a few other options you have (decorators/presenters) outside the scope of this question (but may be worth investigating if you're interested in design/application structure)
If you move it into the Chair model in chair.rb, you can write it as:
def acronym
first_letters = []
name.split.each do |word|
first_letters << word[0]
end
first_letters.join
# Alternative, one line
# name.split.map(&:chars).map(&:first).join('')
end
Then in your controller you can call #chair.acronym
The Rails way is supposed to put the business logic to in the Model file.
We should put the acronym method logic in our model file and define it as instance method.
Then call acronym method from the controller with #chair object as:
#chair.acronym
Hope it helps...
If you want to setup acronym as a new action on your controller you'll need to update your config/routes.rb. The simplest way would be to just add a static route:
get 'my_controller_name/acronym'
You'll also need a corresponding view to render your result in app/views/my_controller/acronym.erb
If you just want to play around without having to implement a view, you can just render text from your action without using a template (view). Just add this to the end of your acronym action:
render plain: first_letters.join
Everything you want to know is in the rails guide on routing.
Related
I am taking ruby-kickstart (Josh Cheek) challenges and even though I managed to pass all the test there is one thing I cannot understand.
In the exercise you are being asked to override the << method for an instance variable. Specifically here is what you have to do:
In Ruby on Rails, when a person goes to a URL on your site, your
application looks at the url, and maps it to a controller method to
handle the request
My boss wanted to be able to specify what CSS class the body of the
HTML output should have, based on which controller method was being
accessed. It fell to me to provide a method, which, when invoked,
would return a String that could handle the request There are a few
nuances, though. The String you return must be retained during the
object's entire life The method must be able to be called multiple
times The String you return should know how to add new classes: each
class is separated by a space The only method you need to worry about
being invoked is the << method.
(plus a few other irrelevant things)
EXAMPLE:
controller = ApplicationController.new
controller.body_class
#=> ""
controller.body_class << 'admin'
controller.body_class
#=> "admin"
controller.body_class << 'category'
controller.body_class
#=> "admin category"
controller.body_class << 'page' << 'order'
controller.body_class
#=> "admin category page order"
My working solution:
class ApplicationController
def initialize
#body_class = ""
end
def body_class
def #body_class.<<(str)
puts "self is:"+self
return self if self=~/\b#{Regexp.escape(str)}\b/
if self==""
self.concat(str)
else
self.concat(" ")
self.concat(str)
end
end
return #body_class
end
end
Everything works perfectly fine.
But an earlier solution I gave (and it didn't work) was the following
class ApplicationController
attr_accessor :body_class
def initialize
#body_class = ""
end
def #body_class.<<(str)
puts "self is:"+self
return self if self=~/\b#{Regexp.escape(str)}\b/
if self==""
self.concat(str)
else
self.concat(" ")
self.concat(str)
end
end
def body_class #Even this for the way I work it out on my mind is unnecessary
return #body_class
end
end
When someone runs on the second not-working sollution the following
obj = ApplicationController.new
obj.body_class << "Hi"
The << method is not being overridden by the object's singleton.
I do not understand why I have to wrap the singleton methods inside the body_class method. (Mind that in the second solution there is an attr_accessor.
Could anyone enlighten me please!
Thanks!
I do not understand why I have to wrap the singleton methods inside the body_class method.
To access the correct instance variable. When you attempt to override it outside of method, you're in the class context. This code runs at class load time. No instances have been created yet. (And even if instances did exist at this point, #body_class instance variable belongs to class ApplicationController, which is itself an instance of class Class).
You need instance context.
Also I am pretty sure that this problem can be solved without any method patching voodoo. Just provide a different object (conforming to the same API. This is called "duck typing").
class ApplicationController
def body_class
#body_class ||= CompositeClass.new
end
class CompositeClass
def initialize
#classes = []
end
def <<(new_class)
#classes << new_class
end
# I imagine, this is what is ultimately called on ApplicationController#body_class,
# when it's time to render the class in the html.
def to_s
#classes.join(' ')
end
end
end
Didn't test this code, naturally.
BTW, the proper way to do it is to explicitly extend the instance variable:
class A
attr_reader :body_class
def initialize
#body_class = "".extend(Module.new do
def <<(other)
return self if self[/\b#{Regexp.escape(other)}\b/]
concat(' ') unless empty?
concat(other)
end
end)
end
end
I am rather new to Rails, and would greatly appreciate any bit of help. I have created the following method:
def name_fix
name = self.split
mod_name = []
name.each do |n|
n.split("")
if n[0]
n.upcase
else
n.downcase
end
mod_name.push(n)
end
mod_name.join
end
I would like to use this method in my Controller as such:
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name].name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
How can I go about accomplishing this? Will this method reside within my Model or Controller? Previously, I've run into an undefined method error.
Note: I'm sure that there is a way to better write my code. I am grateful for help with that as well.
#app/models/patient.rb
class Patient < ActiveRecord::Base
protected
def name=(value)
mod_name = []
value.split.each do |n|
n.split("")
type = n[0] ? "up" : "down"
n.send("#{type}case")
mod_name.push(n)
end
#name = mod_name.join
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient = Patient.new patient_params
#patient.save ? redirect_to(patients_path) : render(:new)
end
private
def patient_params
params.require(:patient).permit(:name)
end
end
What you're doing is trying to override the setter method, which can be done using the above code. Much more efficient and out of the way.
I have created the following method
Since you're new, let me explain something else.
It is important to note where you're using this method.
You've currently put it in the model, which means you'll have to call it to manipulate some attribute / functionality of any object created with said model.
--
Models - in Rails - build the objects which populate your app. Ruby is an object orientated language, which means that every element of your program should revolve around data objects in some degree.
As you can see above, the method of building objects in your system is really about invoking classes. These classes contain methods which can be called, either at class level (IE invoking the class through the method), or at instance level (IE calling a method on an already invoked object).
This is where you get "class" methods (Model.method) and "instance" methods (#model.method) from:
#app/models/patient.rb
class Patient < ActiveRecord::Base
def explode
#this is an instance method
puts "Instance Explode"
end
def self.explode
#this is a class method
puts "Exploded"
end
end
Thus you can call the following:
#patient = Patient.find params[:id]
#patient.explode #-> "Instance explode"
Patient.explode #-> "Exploded"
--
This is important because it gives you a strict framework of where you should, and shouldn't use methods in your models.
It explains why you have controllers & helpers, and allows you to formulate the best way to structure your application as to get the most out of the least code.
For example...
Your use of #patient.name = params[:params][:name].name_fix is incorrect.
It's wrong because you're calling the instance method .name_fix on a piece of data totally unrelated to your model. If you wanted to use .name_fix in a general sense like this, you'd probably use a helper:
#app/helpers/patients_helper.rb
class PatientsHelper
def name_fix value
# stuff here
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient.name = name_fix params[:patient][:name]
end
end
Since you're using the method to populate the .name attribute of your model, it makes sense to override the name= setter. This will not only provide added functionality, but is much smoother and efficient than any other way.
Methods that are called directly are best put in the Controller (or in ApplicationController if you think more than one controller might want to use it).
These are methods like
# app/controllers/my_controller.rb
def foo(bar)
# do something here
end
def create
id = params[:id]
value = foo(id)
end
If you want a chained method that acts as a property method of whatever you're calling it on. Those are characteristic of how Models work - you have your main model and you call attributes or methods on the instance of that model.
# app/models/my_model.rb
def full_name
first_name + " " + last_name
end
# app/controller/my_controller.rb
def create
id = params[:id]
model = MyModel.find(id)
full_name = model.full_name
end
In your case, you want to call name_fix ON whatever is returned by params[:params][:name], which is (I'm guessing) a String.
You have two options
Modify the String class to define a method named name_fix. I highly recommend against this. It's call "monkeypatching" and shouldn't be done without good reason. Just letting you know you can do it in some cases.
Use a direct method in your controller or ApplicationController like the first example above.
#patient.name = name_fix(params[:params][:name])
Edit: As for your request about a better way to write your code... that's difficult to teach or convey in one answer. I'd say read some open source projects out there to see how people write Ruby and some common idioms used to clean up the code. To get you started, here's how I'd re-write your code
def create
#patient = Patient.new(params[:patient])
# 1. Be descriptive with your method names. `name_fix` is vague
# 2. Why is `:name` nested under another `[:params]` hash?
#patient.name = capitalize_name(params[:name])
if #patient.save
# 1. I think `patient_path` has to be singular
# 2. It needs a `Patient` object to know how to construct the URL
# e.g. `/patients/:id`
redirect_to patient_path(#patient)
else
render :new
end
end
def capitalize_name(full_name)
# Example: julio jones
#
# 1. `split` produces an array => ["julio", "jones"]
# 2. `map` applies a function (`capitalize`) to each element
# => ["Julio", "Jones"]
# 3. `join(" ")` rejoins it => "Julio Jones"
full_name.split.map(&:capitalize).join(" ")
end
Assuming your goal with the name_fix method is just to capitalize the first letter of each name, you could just pass name as an argument and store it as a private method on the Controller:
# app/controllers/patient_controller.rb
private
def name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do
#patient.name = name_fix(params[:params][:name])
in the create method.
OR, you could store this method in the model:
# app/models/patient.rb
def self.name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do this instead, in the controller:
#patient.name = Patient.name_fix(params[:params][:name])
I would also suggest renaming your name_fix method to something like capitalize_name.
update your create method as below
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name]
#patient = #patient.name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
It should work.
I have a set of boolean symbols, inside a controller action
These are currently in 1 action, in the following format
def my_action
setup_stages = [:data_entry_completed, :data_validation_completed]
setup_stages.each do |stage|
do stuff
end
end
I've noticed that I need to make use of these symbols in another action but do not want to replicate them. Is there a way to make this list accessible to multiple actions in the controller so that I can iterate through them without having the list twice?
Just define them as constant:
class MyController < AplicationController
SETUP_STAGES = [:data_entry_completed, :data_validation_completed]
I would personally define it as an instance variable:
class MyClass
def initialize
#setup_stages = [:data_entry_completed, :data_validation_completed]
end
def do_action
#setup_stages.each do |stage|
# do stuff
end
end
def show_stages
puts #setup_stages.to_s
end
end
x = MyClass.new
x.do_action
x.show_stages
A constant is also a good way of defining this but should not be altered, so if for whatever reason you want to add other options to the array dynamically you would be able to do this with an instance variable.
I created a controller which called 'internal releases'.
I want to check that the multi-select objects contains at least one selection each.
In my controller I have:
class InternalReleasesController < ApplicationController
def show
if params[:run].nil?
logger.error "Attempt to get trend result without going through the internal_releases_trend_selection_url"
flash[:no_arguments] = 'You have tried accessing trend results without selecting parameters.'
redirect_to internal_releases_trend_selection_url
else
all_options = Array.new(params[:run][:category_id])
missing_selections = validate_arguments params[:run]
all_options = Array.[]params[:run][:category_id]
logger.debug "all_options is: #{all_options.class}"
end
end
end
I created a simple helper method:
module InternalReleasesHelper
def validate_arguments multiselect_hash
answer = Array.new
multiselect_arr.each do |key, val_arr|
if val_arr.length==1 # therefore, no selection made in this multiselect- the first arg will always be ""
answer << key
end
end
answer
end
end
For some reason I get:
undefined method `validate_arguments' for #<InternalReleasesController:0x007faf08bf9f78>
What might cause this?
Include helper module InternalReleasesHelper into InternalReleasesController class
class InternalReleasesController
include InternalReleasesHelper
end
Helper's method are just available into Views by default, so you should include your helper into controller:
Navigate on internal_releases_controller.rb file and insert following:
include InternalReleasesHelper
I've got a lot of code like this in my app:
if #document.template.name == "Newsletter"
...
end
Which I realise is poor and ugly code. I'm not sure though what alternatives exist for this kind of code. Are there any best practices for it? I hope so. Cheers!
Sample controller code
In this controller code sample it posts an image to Twitter if the name is "Newsletter". I know it's messy, and that a lot of the code should be moved to the model. I'm more concerned about the conditional though.
if #document.template.name == "Newsletter"
source = Magick::Image.read(#document.component.image_newsletter.path).first
overlay = Magick::Image.read(#document.user.logo.path).first.resize_to_fit(source.columns)
rec = Magick::Draw.new
rec.stroke = "##{#document.user.colour1}"
rec.fill = "##{#document.user.colour1}"
rec.rectangle 0, 0, source.rows, 5
lank = source.extent(source.columns, source.rows+overlay.rows, 0 ,0)
combo = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(combo)
client.update_with_media("#{#document.title}: #{#document.remove_html(#document.components.first.body[0..100])}...", open(combo.to_blob))
else
client.update("#{#document.title}: #{#document.remove_html(#document.components.first.body[0..100])}... http://domain.com#{share_path(#document.user.ftp, #document)}")
end
Presenter Pattern to the rescue
app/helpers/application_helper.rb
This will give you convenient access to instantiate a presenter anywhere in any of your views.
Example, if you use present #document it will instantiate a DocumentPresenter.
module ApplicationHelper
def present object, klass = nil
klass ||= "#{object.class}Presenter".constantize
presenter = klass.new object, self
yield presenter if block_given?
presenter
end
end
To override the presenter used, you can do present #document, MyPresenter
app/presenters/document.rb
Your actual presenter. Create as many instance methods as you like and keep all of the view logic in here. You have access to all view helper methods through #template
class DocumentPresenter
def initialize document, template
#document = document
#template = template
end
def name
if #document.template.name == "Newsletter"
# for example ...
#template.link_to 'Newsletter', #template.document_index_path
end
end
def description
#template.content_tag :p, #document.description, class: "description"
end
end
app/views/document/show.html.erb
<% present #document do |document_presenter| %>
<div id="document">
<%= document_presenter.description %>
<%= document_presenter.name %>
</div>
<% end %>
Result
<div id="document">
<p class="description">
lorem ipsum
</p>
Newsletters
</div>
You can learn more about the Presenter Pattern as done by Ryan Bates in his RailsCast episode "Presenters from Scratch"
The only alternative I can think of presently is to move the template-specific code in to the Template model, separated in to individual methods which follow a particular naming convention.
For example, your methods could follow the convention process_x, where x is the name of the template. In this case, the code you posted for the "newsletter" would be in a method called process_newsletter.
I would also create a single point of entry, lets call it process, in the same model, which is responsible for delegating to one of these methods, like so:
class Template < ActiveRecord::Base
... other model code
def process # this is the method to be called from the controller
method_name = "process_#{self.name}" # the name of the method to be called
send method_name # call a method by this name
end
def process_newsletter
# your newsletter code already posted
end
def process_article # another example for illustration purposes
# article specific code
end
end
This not only eliminates the need for template name checking, but also helps to further separate your code, and moves any model-specific stuff away from the controller.