I have an initialize method that accepts a hash to set some instance variables. This is basically what I'm trying to do:
class Ad
DEFAULT_PAGE = 'index'.freeze
DEFAULT_CHANNEL = 'general'.freeze
DEFAULT_AREA = 'general'.freeze
attr_accessor :page, :area, :channel
def initialize args={}
#page = args[:page] || DEFAULT_PAGE
#area = args[:area] || DEFAULT_AREA
#channel = args[:channel] || DEFAULT_CHANNEL
end
# ...
end
I saw a tip to allow dynamic setting of instance variables, but I'm not sure how to also include the default values...
def initialize args={}
args.each do |attr,val|
instance_variable_set("##{attr}", val) unless val.nil?
end
end
Can I refer to a constant dynamically? Or any better ways of doing this sort of thing are welcome!
... I also realize that attr_accessor variables can be set individually. But I just want to do it like this. :)
This one also only creates the instance variables if they are in your defaults hash, so that you don't accidentally create/overwrite other instance variables.
I'm assuming you meant to say unless val.nil?:
def initialize(args={})
defaults = {
:page => DEFAULT_PAGE,
:area => DEFAULT_AREA,
:channel => DEFAULT_CHANNEL
}.merge(args).each do |attr, val|
instance_variable_set("##{attr}", val) if defaults.has_key?(attr) && (not val.nil?)
end if args
end
Try this:
def initialize args={}
defaults = {
:page => DEFAULT_AREA,
:area => DEFAULT_AREA,
:channel => DEFAULT_CHANNEL
}
args.reverse_merge(defaults).each do |attr,val|
instance_variable_set("##{attr}", val) unless val.nil?
end
end
Related
Want to achieve the following code using metaprogramming.
#resource = {}
#voters = {}
#is_upvoted = {}
def resource(comment)
#resource[comment.id]
end
def voters(comment)
#voters[comment.id]
end
def is_upvoted(comment)
#is_upvoted[comment.id]
end
How can I create these methods using ruby metaprogramming and access the hash?
Can you tell me what is wrong in my code ?
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_set("##{attribute}", comment.id)
end
end
This bit seems redundant:
#resource = {}
#voters = {}
#is_upvoted = {}
Since you're already looping an array to do your metaprogramming.
You might try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |comment|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[comment.id]
end
end
end
Which I believe will give you methods roughly like:
class Foo
def resource(comment)
#resource ||= {}
#resource[comment.id]
end
end
Personally, it seems not great to me to have comment.id in your method. Because what if someday you want to use a different attribute (or something else altogether) as the key?
So, I think I would do:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
instance_variable_get("##{attr_sym}")[key]
end
end
end
Now, it seems like you're going to want an easy way to set key-value pairs on your instance variable, so I guess I would try something like:
class Foo
%w(
resource
voters
is_upvoted
).each do |attr_sym|
define_method attr_sym do |key=nil|
instance_variable_set("##{attr_sym}", {}) unless instance_variable_get("##{attr_sym}")
hsh = instance_variable_get("##{attr_sym}")
return hsh[key] if key
hsh
end
end
end
In which case you should be able to do (assuming you have a #comment variable that responds to id):
#comment.id
=> 1
foo = Foo.new
=> #<Foo:0x000056536d7504b0>
foo.resource
=> {}
foo.resource[#comment.id] = :bar
=> :bar
foo.resource
=> {1=>:bar}
foo.resource[#comment.id]
=> :bar
Can you tell me what is wrong in my code ?
It's doing the equivalent of this:
def resource(comment)
#resource = comment.id
end
instance_variable_get would be a better choice.
This is how I used it and it works
['resource', 'voters', 'is_upvoted'].each do |attribute|
define_method("#{attribute}") do |comment|
instance_variable_get("##{attribute}")[comment.id]
end
end
Instead of defining a scope in a class like this:
scope :first_user, -> { first }
And calling it like this: User.first_user
I would like to define a block in another class, that can be called on the user class and works like a Scope:
This code is not working but it should signalize what behaviour I want to achieve:
class Manage
def get_first_user
User.&first_added
end
def first_added
Proc.new { first }
end
end
When I run this code:
a = Manage.new
a.get_first_user
it says me, & undefined method for User. How can I execute the Block defined in first_added on the User model?
How can I in general call a block on a class? Thanks
If I understand your question correctly, you can use class_eval:
>> foo = Proc.new { count }
=> #<Proc:0x007f1aa7cacfd8#(pry):30>
>> Buyer.class_eval(&foo)
(30.6ms) SELECT COUNT(*) FROM "buyers"
=> 1234
Or with your example:
class Manage
def get_first_user
User.class_eval(&first_added)
end
def first_added
Proc.new { first }
end
end
It is not what you want but maybe this will be helpful.
I am not sure if one can call proc on something. I think one can only call proc with something, i.t. in your case passing User as parameter.
def get_first_user
wrap User, &first_added
end
def first_added
Proc.new { |model| model.where(...) }
end
private
def wrap(model, &block)
yield model
end
Here's three ways to call first on User from Manager using:
Object#send:
http://ruby-doc.org/core-2.3.1/Object.html#method-i-send
Module#class_eval:
http://ruby-doc.org/core-2.3.1/Module.html#method-i-class_eval
File manage.rb:
class User
def self.first
puts 'record would probably go here'
end
def self.yielder
print "via yielder => "
self.send(yield) if block_given?
end
def self.blocker(&block)
print "via blocker => "
self.send(block.call) if block_given?
end
def self.evaller(&block)
print "via evaller => "
self.class_eval(block.call) if block_given?
end
end
class Manage
def get_first_user
User.yielder(&first_added)
User.blocker(&first_added)
User.evaller(&first_added)
end
def first_added
Proc.new {"first"}
end
end
a = Manage.new
a.get_first_user
Output:
$ ruby manage.rb
via yielder => record would probably go here
via blocker => record would probably go here
via evaller => record would probably go here
I'm creating my own gem and I want to enable user to save data to multiple NOSQL data stores. How can I make this happen? Where should I place the necessary files?
I've done the same thing in my gem. I think you have created an App folder in your gem/engine. Create another folder called "backend" and create classes for each datastore. For my case I created a seperate for Mongo and Redis
module Memberfier
class RedisStore
def initialize(redis)
#redis = redis
end
def keys
#redis.keys
end
def []=(key, value)
value = nil if value.blank?
#redis[key] = ActiveSupport::JSON.encode(value)
end
def [](key)
#redis[key]
end
def clear_database
#redis.keys.clone.each {|key| #redis.del key }
end
end
end
module Memberfier
class MongoStore
def initialize(collection)
#collection = collection
end
def keys
#collection.distinct :_id
end
def []=(key, value)
value = nil if value.blank?
collection.update({:_id => key},
{'$set' => {:value => ActiveSupport::JSON.encode(value)}},
{:upsert => true, :safe => true})
end
def [](key)
if document = collection.find_one(:_id => key)
document["value"]
else
nil
end
end
def destroy_entry(key)
#collection.remove({:_id => key})
end
def searchable?
true
end
def clear_database
collection.drop
end
private
def collection; #collection; end
end
end
You may have already seen one of Uncle Bob's presentations on application architecture. If not, it's here. I'd recommend having a single boundary object that select models inherit from. That boundary object could have multiple CRUD methods such as find, create, delete. That boundary object could inherit from whatever NOSQL adapter you configure. Example/source: http://hawkins.io/2014/01/pesistence_with_repository_and_query_patterns/
I want add any kind of permissions for my rails models just including one module to the model and defining metadata in one database field. How i can do this?
For example:
Folder < AR::B
#permissions_list = [:is_private, :public_on_negotioation]
#permissions_field = :perms
include Permissions
end
module Permissions
"...?"
end
i want to have methods "is_private?", "is_private", "is_private=" for all items in a #permissions_list variable.
So i can use model in this way:
f = Folder.new
f.is_private = true
f.public_on_negotioation = false
f.save
f.reload
f.is_private?
=> true
f.public_on_negotioation?
=> false
so i wrote next Module:
module Permissions
def self.included(mod)
permissions_list = mod.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
define_method permission.to_sym do
perms_bits[index] == '1'
end
alias_method (permission.to_s << "?").to_sym, permission.to_sym
end
end
def perms_bits
send(self.class.instance_variable_get(:#permissions_field)).to_i.to_s(2).reverse
end
def set_permission(name, weight, options)
permissions_field = self.class.instance_variable_get(:#permissions_field)
if options[name]
self.send("#{permissions_field}=", self.send(permissions_field).to_i + weight.to_i) unless send(name)
elsif options.has_key?("#{name}_off")
self.send("#{permissions_field}=", self.send(permissions_field).to_i - weight.to_i) if send(name)
end
end
def update_perms(options)
permissions_list = self.class.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
set_permission(permission.to_sym, 2**index, options)
end
save
end
end
some improvements?
To extend the answer from mdesantis. The way you can wrap up the permissions code for reuse could be something like this (untested):
class Folder < ActiveRecord::Base
include Permissions
end
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
module Permissions
def self.included(klass)
klass.class_eval do
serialize :permissions, PERMISSIONS_STRUCT
end
klass.include(InstanceMethods)
end
module InstanceMethods
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
end
end
Take a look at ActiveRecord::serialize:
Folder < AR::B
# Must be costant, otherwise Rails will raise an
# ActiveRecord::SerializationTypeMismatch
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
serialize :permissions, PERMISSIONS_STRUCT
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
# The same for public_on_negotiation
end
f = Folder.new
f.is_private = true
f.save
f.reload
f.is_private?
=> true
If you need to dynamically define accessor methods:
Folder < AR::B
[:is_private, :public_on_negotiation].each do |action|
define_method("#{action}?") do
permissions.send action
end
end
# And so on for "#{action}=", ...
end
And remember: refactoring is up to you! :-)
I have a class something like this:
class Product < ActiveRecord::Base
# .... some stuff
def prices
# Make hash like { "Regular" => 10, "Discount" => 8 }
end
end
I grab this from the database and try to_xml on it:
Product.find(id).to_xml(:methods => [:prices])
But if fails at the prices hash
... some XML
<prices>Regular10Discount8</prices>
... some more XML
to_json works as expected.
What's the easiest way to alter the format so it ends up as something like this:
<prices>
<price name="Regular">10</price>
<price name="Discount">8</price>
</prices>
I think You're left with doing the to_xml formatting Yourself :
class Product < ActiveRecord::Base
def prices
...
end
def to_xml(options = {})
super(options) do |xml|
if prices.empty?
xml.tag! 'prices' # empty tag
else
xml.prices do
prices.each do |name, val|
xml.price val, 'name' => name
end
end
end
yield(xml) if block_given?
end
end
end
than just to a Product.find(id).to_xml