I have to edit a Rails application, but I am a novice when it comes to language (I am new to Ruby & Rails). When viewing the source files I raised some questions:
class Card < ActiveRecord::Base
class CardRenderer < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Helpers
self.view_paths = Rails.application.config.paths["app/views"]
helper_method :res_url
def print(_card, _template)
#card = _card
render template: "prints/#{_template}.html"
end
protected
def res_url(_res_name)
"#{Rails.root}/app/views/prints/res/#{_res_name}"
end
end
.......
.......
.......
end
I saw a model that contained a classe inside another class
Do you have a name this language feature?
How I can call this class from an instance of the model?
for example from the controller (Obviously this does not work):
#card::CardRenderer.new.print(#card, #template)
The class that is inside the model class has a function that renders a view
I can make use this function from the controller?
for example from the controller (obviously this does not work either)
render Card::CardRenderer.new.print(#card, #template)
Is that the ideal is that I read a book!, And I'll do it! but in these days I can not (timing problems).
Do you have a name for this language feature?
"nested classes", I think
How I can call this class from an instance of the model?
#card.class::CardRenderer should work
The class that is inside the model class has a function that renders a view. Can I use this function from the controller?
Read the whole thing start to finish:
http://guides.rubyonrails.org/layouts_and_rendering.html
p.s. I have a sneaking suspicion the current design of the app goes completely against the ideas of Model-View-Controller
Related
Given I have a class A which is kind of abstract and encapsulates logic which is needed in decendants B and C.
class A
end
class B < A
end
class C < A
end
Furthermore if have resourceful routing which provides routes for B and C and are therefore handled by the respective controllers.
To dry up things I moved shared code of both conntrollers into an "abstract" controller (never to be instantiated and no routes to its actions):
class AController < ApplicationController
def new(additional_opts)
render locals: {base: "stuff"}.merge(additional_opts)
end
end
class BController < AController
def new
super(foo: 1)
end
end
class CController < AController
def new
super(bar: 1)
end
end
A controller action usally has no parameters. But since the AController is intended to be abstract this approach may be valid, or is it better to rely on instance variables and simply call super and then pulling the needed information from the variables instead?
Any insights welcome.
Edit 1:
Thankfully Lateralu42 suggested Concerns which gets me thinking about; ok what is my real question here i want to have an anwser for? (Like in hitch hikers guide). So it is also
about then to use which method of code reuse?
Found a nice blog post here.
Actually, I think your problem could be solved using the concerns pattern (module shared between controllers or models) : How to use concerns in Rails 4
Suppose I have a function trim_string(string) that I want to use throughout my Rails app, in both a model and a controller. If I put it in application helper, it gets into the controller. But application helper isn't required from within models typically. So where do you put common code that you'd want to use in both models and controllers?
In answer to the specific question "where do you put common code that you'd want to use in both models and controllers?":
Put it in the lib folder. Files in the lib folder will be loaded and modules therein will be available.
In more detail, using the specific example in the question:
# lib/my_utilities.rb
module MyUtilities
def trim_string(string)
do_something
end
end
Then in controller or model where you want this:
# models/foo.rb
require 'my_utilities'
class Foo < ActiveRecord::Base
include MyUtilities
def foo(a_string)
trim_string(a_string)
do_more_stuff
end
end
# controllers/foos_controller.rb
require 'my_utilities'
class FoosController < ApplicationController
include MyUtilities
def show
#foo = Foo.find(params[:id])
#foo_name = trim_string(#foo.name)
end
end
It looks like you want to have a method on the String class to "trim" itself better than a trim_string function, right? can't you use the strip method? http://www.ruby-doc.org/core-2.1.0/String.html#method-i-strip
You can add new methods to the string class on an initializer, check this In Rails, how to add a new method to String class?
class String
def trim
do_something_and_return_that
end
def trim!
do_something_on_itself
end
end
That way you can do:
s = ' with spaces '
another_s = s.trim #trim and save to another
s.trim! #trim itself
but check the String class, it looks like you already have what you need there
I'm doing a bit of metaprogramming in Ruby. I'm writing a library to meta-define some methods for me, specifically in the controller (automate some find_by methods that I have to write for my applications).
Currently I generate these methods by having to pass the name of the model for a particular controller into my meta-programming method. Is there a method in a controller that is tied to an ActiveRecord model.
So, here is a poor example
module AwesomeGem
module ClassMethods
def write_some_methods_for(model)
raise "Class #{model.class} does not inherit ActiveRecord::Base" unless model < ActiveRecord::Base
define_method "money_remaining" do |id=nil|
moolah = id ? model.find(id).money : model.find(params[:id]).money
render text: moolah
end
define_method "money_remaining_poller" do |id=nil|
obj = id ? model.find(id) : model.find(params[:id])
# composes some ajax
render js: moneyjs
moneyjs
end
end
end
end
So, to use this method, I plan to
GamblerController < ApplicationController
write_some_methods_for Gambler
end
Again, how could I make it so I don't have to pass the Gambler class to my method? Is there some sort of method or attribute that I could just call the model directly? eg. self.send(:model)
A simple question with a complex explanation.
Controllers are not tied to a particular model by default. You can have a controller playing with several different models, or even a controller using no model at all.
If you still want your code to work automatically in "classic" cases, you could look at the controller's name, and look for a model with the same name (following rails naming conventions).
This is going to sound strange, but hear me out...I need to be able to make the equivalent of a POST request to one of my other controllers. The SimpleController is basically a simplified version of a more verbose controller. How can I do this appropriately?
class VerboseController < ApplicationController
def create
# lots of required params
end
end
class SimpleController < ApplicationController
def create
# prepare the params required for VerboseController.create
# now call the VerboseController.create with the new params
end
end
Maybe I am over-thinking this, but I don't know how to do this.
Inter-controller communication in a Rails app (or any web app following the same model-adapter-view pattern for that matter) is something you should actively avoid. When you are tempted to do so consider it a sign that you are fighting the patterns and framework your app is built on and that you are relying on logic has been implemented at the wrong layer of your application.
As #ismaelga suggested in a comment; both controllers should invoke some common component to handle this shared behavior and keep your controllers "skinny". In Rails that's often a method on a model object, especially for the sort of creation behavior you seem to be worried about in this case.
You shouldn't be doing this. Are you creating a model? Then having two class methods on the model would be much better. It also separates the code much better. Then you can use the methods not only in controllers but also background jobs (etc.) in the future.
For example if you're creating a Person:
class VerboseController < ApplicationController
def create
Person.verbose_create(params)
end
end
class SimpleController < ApplicationController
def create
Person.simple_create(params)
end
end
Then in the Person-model you could go like this:
class Person
def self.verbose_create(options)
# ... do the creating stuff here
end
def self.simple_create(options)
# Prepare the options as you were trying to do in the controller...
prepared_options = options.merge(some: "option")
# ... and pass them to the verbose_create method
verbose_create(prepared_options)
end
end
I hope this can help a little. :-)
In models and controllers, we often use Rails macros like before_validation, skip_before_filter on top of the class definition.
How is this implemented? How do I add custom ones?
Thanks!
They're just standard Ruby functions. Ruby's flexible approach to syntax makes it look better than it is. You can create your own simply by writing your method as a normal Ruby function and doing one of the following:
putting it somewhere that's accessible by your controllers such as application.rb
putting it in a file and requiring it in.
mixing the code into a class via the Ruby include keyword.
That last option is great for model classes and the first option is really only for controllers.
An Example
An example of the first approach is shown below. In this example we add code into the ApplicationController class (in application.rb) and use it in the other controllers.
class BusinessEntitiesController < ApplicationController
nested_within :Glossary
private
# Standard controller code here ....
The nested_within provides helper functions and variables to help identify the id of the "parent" resource. In effect it parses the URL on the fly and is accessible by every one of our controllers. For example when a request comes into the controller, it is automatically parsed and the class attribute #parent_resource is set to the result of a Rails find. A side effect is that a "Not Found" response is sent back if the parent resource doesn't exist. That saves us from typing boiler plate code in every nested resource.
That all sounds pretty clever but it is just a standard Ruby function at heart ...
def self.nested_within(resource)
#
# Add a filter to the about-to-be-created method find_parent_id
#
before_filter :find_parent_id
#
# Work out what the names of things
#
resource_name = "#{resource.to_s.tableize.singularize}"
resource_id = "#{resource_name}_id"
resource_path = "#{resource.to_s.tableize}_path"
#
# Get a reference to the find method in the model layer
#
finder = instance_eval("#{resource}.method :find_#{resource_name}")
#
# Create a new method which gets executed by the before_filter above
#
define_method(:find_parent_id) do
#parent_resource = finder.call(params[resource_id])
head :status => :not_found, :location => resource_path
unless #parent_resource
end
end
The nested_within function is defined in ApplicationController (controllers/application.rb) and therefore gets pulled in automatically.
Note that nested_within gets executed inside the body of the controller class. This adds the method find_parent_id to the controller.
Summary
A combination of Ruby's flexible syntax and Rail's convention-over-configuration makes this all look more powerful (or weirder) than it actually is.
Next time you find a cool method, just stick a breakpoint in front of it and trace through it. Ahh Open Source!
Let me know if I can help further or if you want some pointers on how that nested_within code works.
Chris
Chris's answer is right. But here's where you want to throw your code to write your own:
The easiest way to add Controller methods like that is to define it in ApplicationController:
class ApplicationController < ActionController::Base
...
def self.acts_as_awesome
do_awesome_things
end
end
Then you can access it from individual controllers like so:
class AwesomeController < ApplicationController
acts_as_awesome
end
For models, you want to reopen ActiveRecord::Base:
module ActiveRecord
class Base
def self.acts_as_super_awesome
do_more_awesome_stuff
end
end
end
I personally would put that in a file in config/initializers so that it gets loaded once, and so that I know where to look for it always.
Then you can access it in models like so:
class MySuperAwesomeModel < ActiveRecord::Base
acts_as_super_awesome
end