Including Service class in Controller - ruby

I want to include the following class from my services folder into my Controller..
Here is the Class in ..services/product_service.rb
class MyServices
class << self
def screen_print
"These are the words in screen print"
end
end
end
And all I want to do is this in my controller:
class AmazonsController < ApplicationController
def index
#joe = MyServices.screen_print
end
end
I thought I could just include it in the controller. And its not a module so include isn't working, and I tried updating my config/appliaction.rb file and that didn't work either..

Your class name needs to be the same as the name of your file, I believe. So since your file is named product_service.rb, your class should be:
class ProductService
class << self
def screen_print
"These are the words in screen print"
end
end
end
and in your controller:
class AmazonsController < ApplicationController
def index
#joe = ProductService.screen_print
end
end

In addition to the naming problems already pointed out, Rails won't automatically require arbitrary files from folders it doesn't know about.
If you want files in a new folder to be automatically required, you need to add it to Rails' autoload paths:
# config/application.rb
config.autoload_paths << Rails.root.join('services')
See Auto-loading lib files in Rails 4 for more details.

Rails does not load files from uncommon locations. You will need to tells Rails that the services folder exists and to load file from it.
Add the following to your config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += [Rails.root.join('app', 'services')]

Related

Versioning model serialization

I've a model and 2 controllers as follow :
class MyModel < ActiveRecord::Base
def serializable_hash(options={})
super(only: [:id, :foo])
end
end
module V1
class MyController < ApplicationController
def show
render json: {my_model: #my_model}
end
end
end
module V2
class MyController < ApplicationController
def show
render json: {my_model: #my_model}
end
end
end
I want to be able to return a different json depending on the controller :
class MyModel < ActiveRecord::Base
def serializable_hash(options={})
# If V1
super(only: [:id, :foo])
# ElsIf V2
super(only: [:id, :bar])
# End
end
end
I would like to find a generic solution, so I don't have to send the version manually in parameters.
It would be better to decouple the serialization from your model i.e. don't put the serialization code in the model. You have a few options: Using a json builder in your views directory, or using ActiveModelSerializers. Both approaches will make it easy to version your serialization code:
JSON builders:
# app/views/v1/my_controller/my_model.json.builder
json.my_model do
json.id #my_model.id
json.foo #my_model.foo
end
With the above set up you can imagine easily adding a v2 directory with a different serializer.
In your controller you don't even need to specify a render call since rails will notice you have a builder in your controller's view directory. More info about jbuilder in this Railscast
ActiveModelSerializers:
Same goes with serializers, they are just files in app/serializers. You can put your files in app/serializers/v1/my_model_serializer.rb.
The concept with these two approaches is to use Rails modular paths to version your files. A controller in app/controller/v1 will load files from other directories with the same v1: app/serializers/v1.
Here's the railscast for active model serializers. And the asciicast.
Also, here's a gem that helps with api versioning: https://github.com/EDMC/api-versions I use it and find it nice to work with.

Who can explain how this code block works: Carrierwave MD5 as filename

I am using the Carrierwave gem with a Sinatra app to upload and store images.
I am following How to: Use file's MD5 as filename and everything works as expected. However I do not understand how the following piece of code provided on the how-to page works:
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def md5
chunk = model.send(mounted_as)
#md5 ||= Digest::MD5.hexdigest(chunk.read)
end
def filename
#name ||= "#{md5}#{File.extname(super)}" if super
end
end
I in particular do not understand what model.send(mounted_as)does, what the ||= operator means, and why the if super conditional is used (and what it does).
Could somebody please explain this to me?
Say, for example the model is Person and the ImageUploader is mounted as avatar.
class Person < ActiveRecord::Base
mount_uploader :avatar, ImageUploader
end
Then, the md5 method would be calling something to the affect of chunk = person.avatar and using this to calculate the hash of the file contents, which you want for the name.
The filename method checking to see if there is a #filename instance variable, as defined in the CarrierWave::Uploader::Store class.
class CarrierWave::Uploader::Store
def filename
#filename
end
end
Then calling this again to get the filename extension to use in the constructed filename. The #name variable is then just a temporary cache of the name, so that future calls to the method do not require the whole thing to be calculated again.
Edit:
The carrierwave uploader has methods/instance variables for the model (eg Person instance) and declared mount point in the model (eg avatar). These are from the mount_uploader declaration in your active record model.
module CarrierWave::Uploader::Mountable
attr_reader :model, :mounted_as
def initialize(model=nil, mounted_as=nil)
#model = model
#mounted_as = mounted_as
end
end
These are used for various things, as well as being made available for us to do things just such as you are trying. It is just an abstract way to call person.avatar, which returns the file (File instance not string path). This is then read into the MD5 lib, which gives the hexdigest.
Rewriting this in more plain terms
class ImageUploader < CarrierWave::Uploader::Base
def md5
uploaded_file = model.send(mounted_as) # person.avatar (File instance)
#md5 ||= Digest::MD5.hexdigest(uploaded_file.read) # hexdigest of file content
end
end

Rails Server restart every time I make a change

I have a module file that I need to use for my rails project. When I make a change to any Rails models, views, controller etc the server doesn't need to restart. but when I make a change in that module I need to restart the server.
The module.rb does not inherit anything from Rails classes.
The structure is this:
class_1.rb < class_2.rb includes module.rb
class_1.rb, class_2.rb are also not ActiveRecord classes.
They are all located in my models' directory.
My config/enviroments/development.rb file is correct, as it has this:
config.cache_classes = false
Update: For rails 3.2.9 this should work right out of the box !
Here is what i tried and it works without restarting the server:
# ../models/a.rb
class A
include SomeModule
def test
" test:a"
end
end
# ../models/b.rb
class B < A
def test
super + " test:b"
end
end
# ../models/some_module.rb
module SomeModule
def call_test
test + " test:module"
end
end
# ../controllers/home_controller.rb
class HomeController < ApplicationController
def index
#i = B.new.call_test
end
end
You can place this inside your application.rb when your module doesnt live within the autoload paths:
# Autoload lib/ folder including all subdirectories
config.autoload_paths += Dir["#{config.root}/your_module_folder/**/"]

How to call a page-object from a class.rb at Support folder

I am using the page-object gem. Suppose i have a page-object on features/bussines/pages/booking_page.rb for a page like:
class Booking
include PageObject
span(:txtFirstName, :id => 'details_first_name')
end
...and i use a "tools" class located at features/support/tools.rb with something like:
class MyTools
def call_to_page_object
on Booking do |page|
puts page.txtFirstName
end
end
end
...but this approach fails because calling to the object from the class is not allowed:
undefined method `on' for #<Booking:0x108f5b0c8> (NoMethodError)
Pretty sure i'm missing some concept on the way to use the page-object from a class but don't realize whats the problem. Can you please give me an idea about what could be wrong here, please?
Thank you very much!
============================
Justin found the reason why the call to the class crash. The final class code results:
class MyTools
#Include this module so that the class has the 'on' method
include PageObject::PageFactory
def initialize(browser)
#Assign a browser object to #browser, which the 'on' method assumes to exist
#browser = browser
end
def getCurrentRewards
on Booking do |page|
rewards_text = page.rewards_amount
rewards_amount = rewards_text.match(/(\d+.*\d*)/)[1].to_f
puts "The current rewards amount are: #{rewards_amount}."
return rewards_amount
end
end
end
And the call to the function:
user_rewards = UserData.new(#browser).getCurrentRewards
Why it did not work me? Two main reasons:
I didn't pass the browser object to the class <== REQUIRED
I didn't include the PageObject::PageFactory in the class <== REQUIRED for the "on" method.
Thanks all!
To use the on (or on_page) method requires two things:
The method to be available, which is done by including the PageObject::PageFactory module.
Having a #browser variable (within the scope of the class) that is the browser.
So you could make your MyTools class work by doing:
class MyTools
#Include this module so that the class has the 'on' method
include PageObject::PageFactory
def initialize(browser)
#Assign a browser object to #browser, which the 'on' method assumes to exist
#browser = browser
end
def call_to_page_object
on Booking do |page|
puts page.txtFirstName
end
end
end
You would then be calling your MyTools class like:
#Assuming your Cucumber steps have the the browser stored in #browser:
MyTools.new(#browser).call_to_page_object
What are you trying to do?
Did you read Cucumber & Cheese book?
Pages should be in the features/support/pages folder. You can put other files that pages need there too.
If you want to use on method in a class, you have to add this to the class:
include PageObject
The code from MyTools class looks to me like it should be in Cucumber step file, not in a class.
Your class should use the extend keyword to access special class methods like span:
class Booking
extend PageObject
span(:txtFirstName, :id => 'details_first_name')
end
I hope this works.

Monkey patch class methods

I'm trying to do some monkey patching in ActiveShipping UPS class .
I need to add a class level method (starting with .self), so here it's what I'm trying to do:
module ActiveMerchant
module Shipping
class UPS < Carrier
def self.process_request(receiver, sender, packages, options = {})
# some code
end
def regular_method
"foobar"
end
end
end
end
Unfortunately when I'm trying to use it:
ActiveMerchant::Shipping::UPS.process_request(receiver etc)
I get an error:
NoMethodError: undefined method `process_request' for ActiveMerchant::Shipping::UPS:Class
from (irb):6
from C:/Ruby19/bin/irb.bat:19:in `<main>'
There is no class method named process_request in original class.
In original UPS class provided in gem there is one static method defined self.retry_safe = true
and I can use it without errors.
I can also use regular_method after creating instance of UPS class.
More details provided:
I'm working with Rails 2.3 ( :-( ) and Ruby 1.9.2. I have no influce on environment.
Monkey patched code is under plugins/my_plugin/lib/active_shipping/ext/carriers/ups.rb
In /active_shipping I have file named extensions.rb in which i have:
require 'active_shipping'
require_relative 'ext/carriers'
require_relative 'ext/carriers/ups'
It deals with loading everything properly (I suppose basing on regular_method beheaviour from first chunk of code in my question).
I try to invoke process_request in one of my Controllers. This part is little tricky, beacuse i'm using sth like this:
MyModel.courier_service.process_request(parameters)
where courier_service, in this case holds the ActiveMerchant::Shipping::UPS class.
I'm still a newbie in Ruby and don't know what sort of details i should provide.
Maybe you want to do it in another way
File patch_classes.rb:
module ActiveMerchantExpand
module Shipping
module ClassMethods
def self.process_request(receiver, sender, packages, options = {})
# some code
end
end
module InstanceMethods
def regular_method
"foobar"
end
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
end
Then you have to load your class "ActiveMerchant::Shipping::UPS"
and after that you can attach your methods to your class via
Rails.configuration.to_prepare do
require_dependency [[file for ActiveMerchant::Shipping::UPS]]
require 'patch_classes' )
ActiveMerchant::Shipping::UPS.send(:include, ::ActiveMerchantExpand::Shipping)
end
This is from rails plugin writing, i hope this helps.
regards tingel2k
Do you explicitly require file with your monkey patch? If you just put it under your app or lib path without requiring, it wouldn't load because constant ActiveMerchant::Shipping::UPS is defined in gem and it doesn't trigger dependency resolution mechanism.

Resources