Overload each method with order - ruby

I have an erb template which generates the config file for an httpd.
It's important that a particular location is written last (it is a catch all)
At the moment the code looks like
cluster.apps.each do |app|
# Render config
end
I'd like to overload the each method on the apps object to guarantee order. What's the best place to start looking for how to do this?

If you want to overload it, you can do something like
class Cluster
#..code
def each_application
return unless block_given? #ensure a block was given
a = #apps.shift #Implement this to grab the element you want
#apps.each{|x| yield x}
yield a #yield the element that you want last
end
end
So you can now do:
cluster.each_application do |app|
#Render config
end
And with the current implementation above, it will yield all the elements(except the first one) in a row. The last yielded item is the first one that was shifted off.

Related

How to call after_update callback for a particular column updation?

I have one model called points and in that i have 2 columns named clicked_at and opened_at. Those two columns will not be entered while creating record. Both the columns will be updated manually. Now i want to call a callback only for updating clicked_at column. Is there any way to do this? Help will be appreciated.
You have to check it manually.
def my_callback
if clicked_at_changed?
clicked_at_did_change
end
end
def clicked_at_did_change
# do stuff
end
for this ..observers will be a good option and there are many options when you want to call your code..while updating/creating/deleting/editing/saving ...it goes on.
class PointObserver < ActiveRecord::Observer
##this method will be called everytime AFTER you update point object
def after_update(point)
point.clicked_at =Time.now
point.opened_at =Time.now
point.save!
end
end
enable this observer in application.rb
# Activate observers that should always be running.
config.active_record.observers = :point_observer
You can do whatever you want and also you have many callbacks such as before_save,before_update..etc.
Moreover you can place all your observers in app/observers/..

read json in Ruby and set variables for use in another class

The need here is to read a json file and to make the variables which is done from one class and use them with in another class. What I have so far is
helper.rb
class MAGEINSTALLER_Helper
#note nonrelated items removed
require 'fileutils'
#REFACTOR THIS LATER
def load_settings()
require 'json'
file = File.open("scripts/installer_settings.json", "rb")
contents = file.read
file.close
#note this should be changed for a better content check.. ie:valid json
#so it's a hack for now
if contents.length > 5
begin
parsed = JSON.parse(contents)
rescue SystemCallError
puts "must redo the settings file"
else
puts parsed['bs_mode']
parsed.each do |key, value|
puts "#{key}=>#{value}"
instance_variable_set("#" + key, value) #better way?
end
end
else
puts "must redo the settings file"
end
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
Which is called in a file of Pre_start.rb
class Pre_start
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
mi_h = MAGEINSTALLER_Helper.new
mi_h.load_settings()
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
file="www/depo/newfile-#{bs_MAGEversion}.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{bs_MAGEversion}/file-#{bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
the josn file is valid json and is a simple object (note there is more just showing the relevant)
{
"bs_mode":"lite",
"bs_MAGEversion":"1.8.0.0"
}
The reason I need to have a json settings file is that I will need to pull settings from a bash script and later a php script. This file is the common thread that is used to pass settings each share and need to match.
Right now I end up with an empty string for the value.
The instance_variable_setis creating the variable inside MAGEINSTALLER_Helper class. That's the reason why you can't access these variables.
You can refactor it into a module, like this:
require 'fileutils'
require 'json'
module MAGEINSTALLER_Helper
#note nonrelated items removed
#REFACTOR THIS LATER
def load_settings()
content = begin
JSON.load_file('scripts/installer_settings.json')
rescue
puts 'must redo the settings file'
{} # return an empty Hash object
end
parsed.each {|key, value| instance_variable_set("##{key}", value)}
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
class PreStart
include MAGEINSTALLER_Helper
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
load_settings # The method is available inside the class
file="www/depo/newfile-#{#bs_MAGEversion}.tar.gz"
if !File.exist?(file)
download("http://www.dom.com/#{#bs_MAGEversion}/file-#{#bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
I refactored a little bit to more Rubish style.
On this line:
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
instance_variable_get isn't retrieving from the mi_h Object, which is where your value is stored. The way you've used it, that line is equivalent to:
bs_MAGEversion=#bs_MAGEversion
Changing it to mi_h.instance_variable_get would work. It would also be painfully ugly ruby. But I sense that's not quite what you're after. If I read you correctly, you want this line:
mi_h.load_settings()
to populate #bs_MAGEversion and #bs_mode in your Pre_start object. Ruby doesn't quite work that way. The closest thing to what you're looking for here would probably be a mixin, as described here:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
We do something similar to this all the time in code at work. The problem, and solution, is proper use of variables and scoping in the main level of your code. We use YAML, you're using JSON, but the idea is the same.
Typically we define a constant, like CONFIG, which we load the YAML into, in our main code, and which is then available in all the code we require. For you, using JSON instead:
require 'json'
require_relative 'helper'
CONFIG = JSON.load_file('path/to/json')
At this point CONFIG would be available to the top-level code and in "helper.rb" code.
As an alternate way of doing it, just load your JSON in either file. The load-time is negligible and it'll still be the same data.
Since the JSON data should be static for the run-time of the program, it's OK to use it in a CONSTANT. Storing it in an instance variable only makes sense if the data would vary from instance to instance of the code, which makes no sense when you're loading data from a JSON or YAML-type file.
Also, notice that I'm using a method from the JSON class. Don't go through the rigamarole you're using to try to copy the JSON into the instance variable.
Stripping your code down as an example:
require 'fileutils'
require 'json'
CONTENTS = JSON.load_file('scripts/installer_settings.json')
class MAGEINSTALLER_Helper
def download(from,to)
puts "completed download for #{from}\n"
end
end
class Pre_start
def initialize(params=nil)
file = "www/depo/newfile-#{ CONFIG['bs_MAGEversion'] }.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{ CONFIG['bs_MAGEversion'] }/file-#{ CONFIG['bs_MAGEversion'] }.tar.gz", file)
else
puts "mage package exists"
end
end
end
CONFIG can be initialized/loaded in either file, just do it from the top-level before you need to access the contents.
Remember, Ruby starts executing it at the top of the first file and reads downward. Code that is outside of def, class and module blocks gets executed as it's encountered, so the CONFIG initialization will happen as soon as Ruby sees that code. If that happens before you start calling your methods and creating instances of classes then your code will be happy.

Using shove to add a string to an array in ruby

As part of an online Ruby tutorial, I must create a text-based game. One requirement is that I use require to pull in another file. I've done that as well as include the module that holds a method. However, I cannot produce the result I want. Here's my file with the module:
module Inventory
def Inventory.inventory(item)
items = Array.new
if item == "show"
items.inspect
else
items << item
end
end
end
I want the parameter (item) to be added to the array items as a string that can be inspected when I pass the "show" argument to it.
So for example, I want to add a 'bat' to the inventory so I call Inventory.inventory("bat"). Later I'd like to add other things. But when I call Inventory.inventory("show") it doesn't show anything.
I've spent days looking through many other tutorials and hundreds of questions here but still can't get it work. I'm probably not understanding something really fundamental so please be gracious to me as I'm still learning.
Is it the way I'm adding to an array? The way I'm trying to get it to show? Or do I not understand how to use methods and arguments?
you can Dylan's answer if you want to go with instance approach or you can use class variables.
The problem with your code is that you initialize items local variable every time you call inventory.
Here is a version that will persist items in a class variable:
module Inventory
def Inventory.inventory(item)
##items ||= Array.new
if item == "show"
##items.inspect
else
##items << item
end
end
end
Inventory.inventory 1
Inventory.inventory 2
p Inventory.inventory 'show'
this is producing
"[1, 2]"
This would make a lot more sense as a class. This way, you can store the items in an instance variable that will persist during multiple calls to add, show, etc. You can of course put this class into a separate file and still include it.
class Inventory
def initialize
#items = []
end
def add(item)
#items << item
end
def show
#items.inspect
end
end
# To use the class:
inventory = Inventory.new
inventory.add('bat')
inventory.show
# => ["bat"]
The issue is that your items array is being recreated every time this method is called, so there is no persistence between method calls for what is passed into the array. Dylan Markow's answer shows how you can use instance variables to persist values between method calls.

Two versions of each blog post in Jekyll

I need two versions of each of my posts in a very simple Jekyll setup: The public facing version and a barebones version with branding specifically for embedding.
I have one layout for each type:
post.html
post_embed.html
I could accomplish this just fine by making duplicates of each post file with different layouts in the front matter, but that's obviously a terrible way to do it. There must be a simpler solution, either at the level of the command line or in the front matter?
Update:
This SO question covers creating JSON files for each post. I really just need a generator to loop through each post, alter one value in the YAML front matter (embed_page=True) and feed it back to the same template. So each post is rendered twice, once with embed_page true and one with it false. Still don't have a full grasp of generators.
Here's my Jekyll plugin to accomplish this. It's probably absurdly inefficient, but I've been writing in Ruby for all of two days.
module Jekyll
# override write and destination functions to taking optional argument for pagename
class Post
def destination(dest, pagename)
# The url needs to be unescaped in order to preserve the correct filename
path = File.join(dest, CGI.unescape(self.url))
path = File.join(path, pagename) if template[/\.html$/].nil?
path
end
def write(dest, pagename="index.html")
path = destination(dest, pagename)
puts path
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'w') do |f|
f.write(self.output)
end
end
end
# the cleanup function was erasing our work
class Site
def cleanup
end
end
class EmbedPostGenerator < Generator
safe true
priority :low
def generate(site)
site.posts.each do |post|
if post.data["embeddable"]
post.data["is_embed"] = true
post.render(site.layouts, site.site_payload)
post.write(site.dest, "embed.html")
post.data["is_embed"] = false
end
end
end
end
end

How to create a new DataMapper type?

I was following the guide from the DataMapper site, and this is what I have:
module DataMapper
class Property
class SymbolStore < String
def dump(v)
v.to_s
end
def load(v)
v.to_sym
end
def typcast_to_primitive(v)
v.to_s
end
end
end
end
But, I'm getting strange behaviour. When I try to set a value that is a SymbolStore, it comes back as a string. However, if I load a record, it comes back properly, as a symbol. From my experiments, load only seems to be called when a record is loaded, and not when I'm trying to get the value of something I've just set.
In the real world, I should not need to get a value I have just set, but this problem does not make sense.
Try it with typecast_to_primitive (you had a typo).

Resources