Keeping an instance variable as an instance variable, not its contents - ruby-on-rails

I have only a vague idea on phrasing this, so question as needed:
I have a set of values I'm passing in my rails controller on a regular basis to a widget that differs slightly from page to page, from what I pass to it. This is is starting to get unwieldy for every controller, so I added a small class to help concatenate that process a bit (basic starting gist below).
#return dashcontroller hash from more succinct cues
module DashControl
class DashControl
attr_accessor :title, :instance, :actions
def initialize(title='default title', instance='default instance', actions={})
#title = title
#instance = instance
initialize_actions(actions)
end
def initialize_actions(actions)
actions.kind_of?(Hash) ? #actions = actions : initialize_tag(actions)
end
def initialize_tag(tag)
case tag
when :manage_default
#actions = {:statusactions => [],
:formactions => [ ['#tabaccount', 'addaccount'],
['#tabuser', 'addusers'],
['#tabadd','adddomain'] ],
:linkactions => [ [] ],
:filteractions => [ [] ] }
when :none
#actions = {}
#when
# #actions = {}
else
#actions = #actions
end
end
def dashcontroller
{:title => #title, :instance => #instance, :actions => #actions }
end
end
end
So basically I just need to pass an instance of this.dashcontroller and I get the hash I need with a lot less chaos in my controllers . The issue is with the #instance variable. I want to pass in the instance I'm using e.g. #book, #account, etc, and have it come out as #book, #account, etc. Instead, I get the contents of whatever I put into there as :instance => (contents of that instance). It doesn't seem right to me as before I was just using e.g. #account, and then using that, but looking at it might not make any sort of difference in the widget, as I juggle things and work on my code-fu.
Basically my question is how to push an instance variable through a class like this, and still have it accessibile as it went in without having to do any backflips and transformations on the other side. There is probably a better way, but this is what I'm working with at the moment.
edit: pseudo-code
DashControl::DashControl.new("Catchy Title", #book, :none).dashcontroller
#=> {:title => "Catchy Title", :instance => #book, :actions => {} }
I think I can work with it, like I said its more an issue of my understanding of how things flow than an actual bug or anything difficult. I'd like to not have to do more gymnastics on the other end with the instance stuff, though the contents are there and that is all I really need, I just need some input on thinking it through to be less of a mess. I really need to refine what I'm sending through this, or use this to further refine what I'm sending on is the bottom line lesson to take away right now.
edit:
I ended up tossing this, but it was a learning experience...I went back the widget and I know more than when I originally set up the widget, so I've been able to set that up to take only the instance variable and bootstrap what it needs without adding another class, cleaning up my controllers and handing a lot back to the widget where I suspect it should/could have been to start.

Based on your code and example, this fits:
# No need to put a class in a namespace of the same name, just make the module a class
# Also, if you inherit from a struct, it can save you a lot of typing. It defines the setters and getters for you.
class DashControl < Struct.new(:title, :instance, :actions)
# since it looks like you always access it the same way, create a class method
# which does this initialization and invocation
def self.for(*args)
new(*args).dashcontroller
end
def initialize(title='default title', instance='default instance', actions=:none)
# here, we can use our own defaults and normalization and pass the results up to the struct
super title, instance, normalize(actions)
end
# didn't make sense to call this initialize_tag, as it was initializing actions
# also there was already an initialize actions method which just checked for the case of a hash
# but then elsewhere you checked for other things. Better to just put it all in one method and return it
# (then you aren't setting it every time you want to ask it to calculate that value)
# also using guard clauses (the if statements that return early) instead of the case, as they are easier to understand
def normalize(actions)
return Hash.new if actions == :none
return actions unless actions == :manage_default
default_actions
end
# the value of default_actions is complicated and noisy, separate it out to its own method
# this prevents it from cluttering the code around it, and also allows us to access,
# and to do this without the side effects of setting values.
def default_actions
{ :statusactions => [],
:formactions => [ ['#tabaccount', 'addaccount'],
['#tabuser', 'addusers'],
['#tabadd','adddomain'] ],
:linkactions => [ [] ],
:filteractions => [ [] ] }
end
# use the getters instead of the ivars (I consider this a generally best practice -- and you could have
# done it before, since you declared the attr_accessor, even though I'm accessing it through the struct)
def dashcontroller
{:title => title, :instance => instance, :actions => actions }
end
end
DashControl.for # => {:title=>"default title", :instance=>"default instance", :actions=>{}}
DashControl.for('Catchy Title', '#book', :none) # => {:title=>"Catchy Title", :instance=>"#book", :actions=>{}}
DashControl.for('Catchy Title', '#book', :manage_default) # => {:title=>"Catchy Title", :instance=>"#book", :actions=>{:statusactions=>[], :formactions=>[["#tabaccount", "addaccount"], ["#tabuser", "addusers"], ["#tabadd", "adddomain"]], :linkactions=>[[]], :filteractions=>[[]]}}
DashControl.for('Catchy Title', '#book', a: 'b') # => {:title=>"Catchy Title", :instance=>"#book", :actions=>{:a=>"b"}}
DashControl.for('Catchy Title', '#book', 123) # => {:title=>"Catchy Title", :instance=>"#book", :actions=>123}

Related

Time value cached in production mode

I have the following service providing me with an array mapping month/week/today to a time.
module Admin
class TimeService
INTERVAL_TIME = [
{ :id => "month", :from => Time.zone.now.beginning_of_month },
{ :id => "week", :from => Time.zone.now.beginning_of_week },
{ :id => "today", :from => Time.zone.now.beginning_of_day }
]
end
end
In my controller params[:id] is passed, holding either "month"/"week"/"today", to index method.
class TimeController < ApplicationController
def index
#entry = TimeService::INTERVAL_TIME.find {
|item| item[:id] == params[:time_id] }
end
end
Then I use the entry[:from] in my view. Everything works fine in development, but in production the value shown for "today" is usually a couple days back. I guess there might be some caching going on, but I have only been able to find people having trouble with scopes being cached. Anyone know how this can be solved?
In your current implementation, the :from keys will have the values as calculated at them moment your class is loaded, once.
In development mode, the classes are reloaded that's why you don't see the "cached" values there.
The :from keys in your constant should have lambdas as values in order to calculate the values each time they are accessed (you'll have to change the way you access their values since they'll be lambdas).

rails best way to abstract deep object literal data

I have code like this in my controller:
def home
#mainImage = []
#mainImage.push(
{:breakpoint => 1024,
:src => i_path('pages/home-d.jpg'),
:src_2x => i_path('pages/home-d_2x.jpg')}
)
#mainImage.push(
{:breakpoint => 768,
:src => i_path('pages/home-t.jpg'),
:src_2x => i_path('pages/home-t_2x.jpg')}
)
#mainImage.push(
{:breakpoint => 320,
:src => i_path('pages/home-m.jpg'),
:src_2x => i_path('pages/home-m_2x.jpg')}
)
#alt = 'An image description'
#defaultImage = i_path('pages/home-m.jpg')
end
Which in the view is rendered with help of a partial.
I now need to add similar functionality to render model attributes from a paperclip object.
Which is now looking like this:
#respImage.push(
{:breakpoint => 1024,
:src => slide.image.url(:desktop_reg),
:src_2x => slide.image.url(:desktop_retina)}
)
#respImage.push(
{:breakpoint => 768,
:src => slide.image.url(:tablet_reg),
:src_2x => slide.image.url(:tablet_retina)}
)
#...
The final objective of Is to have a slideshow, with many slides.
A slide has several attribute strings and a paperclip attribute.
The paperclip attribute has 6 styles for each image size.
What is the standard mechanism in Rails to transmit data like the above to the view?
I'm assuming this generic array is not the most flexible solution.
This is where the code ended up.
In the controller:
def home
#mainImage2 = RespImage.new(:alt => 'default homepage image')
#mainImage2.add_breakpoint(BREAKPOINTS['desktop'],i_path('pages/home-d.jpg'),i_path('pages/home-d_2x.jpg'));
#mainImage2.add_breakpoint(BREAKPOINTS['tablet'],i_path('pages/home-t.jpg'),i_path('pages/home-t_2x.jpg'));
#...
models/resp_image.rb :
class RespImage
attr_accessor :alt, :breakpoints
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def add_breakpoint(px,src,src_2x)
self.breakpoints ||= [];
self.breakpoints.push RespBreakpoint.new(:px => px, :src => src, :src_2x =>src_2x)
end
def add_paperclip_breakpoints(paperclip)
add_breakpoint(BREAKPOINTS('desktop'), paperclip.url(:desktop_reg), paperclip.url(:desktop_retina));
add_breakpoint(BREAKPOINTS('tablet'), paperclip.url(:tablet_reg), paperclip.url(:tablet_retina));
add_breakpoint(BREAKPOINTS('mobile'), paperclip.url(:mobile_reg), paperclip.url(:mobile_retina));
end
def default_src
self.breakpoints.sort.first.src
end
end
models/resp_breakpoint
class RespBreakpoint
include Comparable
attr_accessor :px,:src,:src_2x
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def better_than?(other)
self.px > other.px
end
def <=>(other)
self.px <=> other.px
end
def eql?(other)
self.px === other.px
end
end
Huge thanks to #Dave Newton below.
First, use value objects. While an array of hashes seems easy at first, VOs can help encapsulate behavior as well as data easily, and improve readability.
Second, consider making those VOs "top-level" models such that they can be rendered using an appropriately-named partial, which I'm assuming would clean up your helper as well.
The last part of your question is a bit opaque without any context: what do you mean by "add similar functionality to render model attributes from a Paperclip object"? Do the objects have the same attributes? Is the data already represented by a class, or by attributes on a class?
If the latter, then you can use duck-typing to eliminate some of the duplication.
Right now it looks like this information is stored a bit funky. I'd probably turn the model around a bit and use a hash, keyed by breakpoint (useful since it's what CSS uses to trigger the style changes) or something more semantic (e.g., :desktop, :tablet, etc.).
Your model would contain the hash keys as attributes (i.e., breakpoint, src, and src_2x).
The values could be stored anywhere, it kind of depends on how often they'd need to change. It might be enough to use constants, or a YAML file, or a Ruby config object, etc.
The array itself would be created by a utility class or service object taking some parameter that determined what information needed to go in it. You might have a method that took something that returned either an i_path or slide image URL depending on what was passed in (think Inversion of Control).
Or it could be a decorator that decorated your slide model and a fake (or real) model for the i_path ones, etc.

Add virtual attribute to json output

Let's say I have an app that handles a TODO list. The list has finished and unfinished items. Now I want to add two virtual attributes to the list object; the count of finished and unfinished items in the list. I also need these to be displayed in the json output.
I have two methods in my model which fetches the unfinished/finished items:
def unfinished_items
self.items.where("status = ?", false)
end
def finished_items
self.items.where("status = ?", true)
end
So, how can I get the count of these two methods in my json output?
I'm using Rails 3.1
The serialization of objects in Rails has two steps:
First, as_json is called to convert the object to a simplified Hash.
Then, to_json is called on the as_json return value to get the final JSON string.
You generally want to leave to_json alone so all you need to do is add your own as_json implementation sort of like this:
def as_json(options = { })
# just in case someone says as_json(nil) and bypasses
# our default...
super((options || { }).merge({
:methods => [:finished_items, :unfinished_items]
}))
end
You could also do it like this:
def as_json(options = { })
h = super(options)
h[:finished] = finished_items
h[:unfinished] = unfinished_items
h
end
if you wanted to use different names for the method-backed values.
If you care about XML and JSON, have a look at serializable_hash.
With Rails 4, you can do the following -
render json: #my_object.to_json(:methods => [:finished_items, :unfinished_items])
Hope this helps somebody who is on the later / latest version
Another way to do this is add this to your model:
def attributes
super.merge({'unfinished' => unfinished_items, 'finished' => finished_items})
end
This would also automatically work for xml serialization.
http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
Be aware though, you might want use strings for the keys, since the method can not deal with symbols when sorting the keys in rails 3. But it is not sorted in rails 4, so there shouldn't be a problem anymore.
just close all of your data into one hash, like
render json: {items: items, finished: finished, unfinished: unfinished}
I just thought I'd provide this answer for anyone like myself, who was trying to integrate this into an existing as_json block:
def as_json(options={})
super(:only => [:id, :longitude, :latitude],
:include => {
:users => {:only => [:id]}
}
).merge({:premium => premium?})
Just tack .merge({}) on to the end of your super()
This will do, without having to do some ugly overridings. If you got a model List for example, you can put this in your controller:
render json: list.attributes.merge({
finished_items: list.finished_items,
unfinished_items: list.unfinished_items
})
As Aswin listed above, :methods will enable you to return a specific model's method/function as a json attribute, in case you have complex assosiations this will do the trick since it will add functions to the existing model/assossiations :D it will work like a charm if you dont want to redefine as_json
Check this code, and please notice how i'm using :methods as well as :include [N+Query is not even an option ;)]
render json: #YOUR_MODEL.to_json(:methods => [:method_1, :method_2], :include => [:company, :surveys, :customer => {:include => [:user]}])
Overwritting as_json function will be way harder in this scenario (specially because you have to add the :include assossiations manually :/
def as_json(options = { })
end
If you want to render an array of objects with their virtual attributes, you can use
render json: many_users.as_json(methods: [:first_name, :last_name])
where first_name and last_name are virtual attributes defined on your model

Question for Ruby Gurus (help with Enum-like implementation)

I am trying to make a sort of "enum". Here is my implementation:
# Format of input hash to AnEnum::initialize is :
# {
# Symbol => [Fixnum => String]
# }
# Example:
# {
# :active => [1 => "Active"]
# }
class AnEnum
##values = nil
def initialize(hash)
##values = hash
end
def values
##values
end
def [](symbol)
values[symbol][0] # return the number for the symbol. e.g. 1
end
def text(symbol)
values[symbol][1] # return the text for the symbol. e.g. "Active"
end
end
Example Usage:
class MyClass1
##status = AnEnum.new({
:open => [1, 'Active'],
:closed => [2, 'Closed']
})
def self.Status
##status
end
end
# test it (it works!)
MyClass1.Status[:open] # => 1
MyClass1.Status.text(:open) # => "Active"
This works, but I want to make it more "elegant" and "dynamic" :
Is it possible to define AnEnum in MyClass2 like this:
class MyClass2
define_enum "Status", :as => {
:open => [1, 'Active'],
:closed => [2, 'Closed']
}
end
So that these will work:
MyClass2.Status[:open] # => 1
MyClass2.Status.text(:open) # => "Active"
Thus, the ##status and self.Status defined in MyClass1 above are automatically included in the class by the "macro"-like call to define_enum.
define_enum is intended to be working like for example the before_filter call in Rails.
Is this possible??
That's great if you're tackling this problem for your own personal gain, however if it's because you actually need this functionality, there are tons of Ruby gems out there that already do this. If you need each "State" to exhibit different behavior, I have written a useful gem called classy_enum. Otherwise, here are a ton of others.
To answer your question though, yes it is definitely possible to add class methods or macros as you are describing. A high level overview would look something like:
module MyEnum
def define_enum(name, states)
... meta code here ...
end
end
Then in your class:
MyClass
extend MyEnum
define_enum :name, :state1 => [], :state2 => []
end
The "meta code" is where it gets tricky depending on what you are trying to do. If you are going to go this route, I would still recommend checking out how others have done it first. You've got a few things in your example that are a little odd, such as capitalized method names (def self.Status) and class variables ##my_var.
Look at this: http://code.dblock.org/ShowPost.aspx?id=184 (slight improvement over http://www.rubyfleebie.com/enumerations-and-ruby/). Lets you write the following.
class Gender
include Enum
Gender.define :MALE, "male"
Gender.define :FEMALE, "female"
end
And of course
Gender.all
Gender::MALE

ruby object array... or hash

I have an object now:
class Items
attr_accessor :item_id, :name, :description, :rating
def initialize(options = {})
options.each {
|k,v|
self.send( "#{k.to_s}=".intern, v)
}
end
end
I have it being assigned as individual objects into an array...
#result = []
some loop>>
#result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>
But instead of assigning my singular object to an array... how could I make the object itself a collection?
Basically want to have the object in such a way so that I can define methods such as
def self.names
#items.each do |item|
item.name
end
end
I hope that makes sense, possibly I am overlooking some grand scheme that would make my life infinitely easier in 2 lines.
A few observations before I post an example of how to rework that.
Giving a class a plural name can lead to a lot of semantic issues when declaring new objects, as in this case you'd call Items.new, implying you're creating several items when in fact actually making one. Use the singular form for individual entities.
Be careful when calling arbitrary methods, as you'll throw an exception on any misses. Either check you can call them first, or rescue from the inevitable disaster where applicable.
One way to approach your problem is to make a custom collection class specifically for Item objects where it can give you the information you need on names and such. For example:
class Item
attr_accessor :item_id, :name, :description, :rating
def initialize(options = { })
options.each do |k,v|
method = :"#{k}="
# Check that the method call is valid before making it
if (respond_to?(method))
self.send(method, v)
else
# If not, produce a meaningful error
raise "Unknown attribute #{k}"
end
end
end
end
class ItemsCollection < Array
# This collection does everything an Array does, plus
# you can add utility methods like names.
def names
collect do |i|
i.name
end
end
end
# Example
# Create a custom collection
items = ItemsCollection.new
# Build a few basic examples
[
{
:item_id => 1,
:name => 'Fastball',
:description => 'Faster than a slowball',
:rating => 2
},
{
:item_id => 2,
:name => 'Jack of Nines',
:description => 'Hypothetical playing card',
:rating => 3
},
{
:item_id => 3,
:name => 'Ruby Book',
:description => 'A book made entirely of precious gems',
:rating => 1
}
].each do |example|
items << Item.new(example)
end
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
Do you know the Ruby key word yield?
I'm not quite sure what exactly you want to do. I have two interpretations of your intentions, so I give an example that makes two completely different things, one of them hopefully answering your question:
class Items
#items = []
class << self
attr_accessor :items
end
attr_accessor :name, :description
def self.each(&args)
#items.each(&args)
end
def initialize(name, description)
#name, #description = name, description
Items.items << self
end
def each(&block)
yield name
yield description
end
end
a = Items.new('mug', 'a big cup')
b = Items.new('cup', 'a small mug')
Items.each {|x| puts x.name}
puts
a.each {|x| puts x}
This outputs
mug
cup
mug
a big cup
Did you ask for something like Items.each or a.each or for something completely different?
Answering just the additional question you asked in your comment to tadman's solution: If you replace in tadman's code the definition of the method names in the class ItemsCollection by
def method_missing(symbol_s, *arguments)
symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
if s == 's' and arguments.empty?
select do |i|
i.respond_to?(symbol) && i.instance_variables.include?("##{symbol}")
end.map {|i| i.send(symbol)}
else
super
end
end
For his example data you will get following outputs:
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
puts items.descriptions.join(', ')
# => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
As I don't know about any way to check if a method name comes from an attribute or from another method (except you redefine attr_accessor, attr, etc in the class Module) I added some sanity checks: I test if the corresponding method and an instance variable of this name exist. As the class ItemsCollection does not enforce that only objects of class Item are added, I select only the elements fulfilling both checks. You can also remove the select and put the test into the map and return nil if the checks fail.
The key is the return value. If not 'return' statement is given, the result of the last statement is returned. You last statement returns a Hash.
Add 'return self' as the last line of initialize and you're golden.
Class Item
def initialize(options = {})
## Do all kinds of stuff.
return self
end
end

Resources