My current sinatra router looks like this but multiple stores are on the way so i would like to replace this case with one line,
get_or_post "/list.json" do
case
when params[:store]=='storeVakanties' then return jsonp(get_data_for(Vakantie))
when params[:store]=='storeDeelnemers' then return jsonp(get_data_for(Deelnemer))
when params[:store]=='storeJobstudenten' then return jsonp(get_data_for(Jobstudent))
end
end
if i rename my ExtJs store to the name of the activerecord class i could do something like
get_or_post "/list.json" do
jsonp(get_data_for(params[:store])) #eg params[:store]='Vakantie'
end
but i need to pass a class, not a string.. any suggestions ?
In the simplest case (no namespaces) you can use:
Object.const_get(params[:store])
If you need to get namespaced constant, you can do
params[:store].split('::').inject(Object) {|klass, name| klass.const_get(name) }
For more advanced implementation have a look at the source of constantize method from ActiveSupport's Inflector: http://apidock.com/rails/v4.0.2/ActiveSupport/Inflector/constantize
If you want to restrict the available classes, something like this would work:
params = { store: "Vakantie" }
[Vakantie, Deelnemer, Jobstudent].find { |c| c.to_s == params[:store] }
#=> Vakantie
I would probably just write a helper method and map the classes explicitly:
helpers do
def store(name)
case name
when 'storeVakanties' then Vakantie
when 'storeDeelnemers' then Deelnemer
when 'storeJobstudenten' then Jobstudent
end
end
end
get_or_post "/list.json" do
jsonp(get_data_for(store(params[:store])))
end
You could use eval:
get_or_post "/list.json" do
jsonp(get_data_for(eval(params[:store])))
end
If you’re horrified by that, you could use const_get:
Kernel.const_get(params[:store])
You’ll need to perform some validation, though, because it’s dangerous to allow user input to select any defined class.
I found a solution myself using ActiveSupport's constantize
jsonp(get_data_for(params[:store].constantize))
In my case security isn't an issue but i like the solution from Stefan the best so i give him the credits.
Related
I have the following Ruby code:
module BigTime
FOO1_MONEY_PIT = 500
FOO2_MONEY_PIT = 501
class LoseMoney
##SiteName = 'FOO1'
#site_num = ##SiteName_MONEY_PIT
def other_unimportant_stuff
whatever
end
end
end
So, what I'm trying to do here is set the SiteName and then use SiteName and combine it with the string _MONEY_PIT so I can access FOO1_MONEY_PIT and store its contents (500 in this case) in #site_num. Of course, the above code doesn't work, but there must be a way I can do this?
Thanks!!
If you want to dynamically get the value of a constant, you can use Module#const_get:
module BigTime
FOO1_MONEY_PIT = 500
FOO2_MONEY_PIT = 501
class LoseMoney
##SiteName = 'FOO1'
#site_num = BigTime.const_get(:"#{##SiteName}_MONEY_PIT")
end
end
Do not, under any circumstance, use Kernel#eval for this. Kernel#eval is extremely dangerous in any context where there is even the slightest possibility that an attacker may be able to control parts of the argument.
For example, if a user can choose the name of the site, and they name their site require 'fileutils'; FileUtils.rm_rf('/'), then Ruby will happily evaluate that code, just like you told it to!
Kernel#eval is very dangerous and you should not get into the habit of just throwing an eval at a problem. It is a very specialized tool that should only be employed when there is no other option (spoiler alert: there almost always is another option), and only after a thorough security review.
Please note that dynamically constructing variable names is already a code smell by itself, regardless of whether you use eval or not. It pretty much always points to a design flaw somewhere. In general, you can almost guaranteed replace the multiple variables with a data structure. E.g. in this case something like this:
module BigTime
MONEY_PITS = {
'FOO1' => 500,
'FOO2' => 501,
}.freeze
class LoseMoney
##SiteName = 'FOO1'
#site_num = MONEY_PITS[##SiteName]
end
end
You can refactor this as to use a Hash for your name lookups, and a getter method to retrieve it for easy testing/validation. For example:
module BigTime
MONEY_PITS = { FOO1: 500, FOO2: 501 }
MONEY_PIT_SUFFIX = '_MONEY_PIT'
class LoseMoney
##site = :FOO1
def initialize
site_name
end
def site_name
#site_name ||= '%d%s' % [MONEY_PITS[##site], MONEY_PIT_SUFFIX]
end
end
end
BigTime::LoseMoney.new.site_name
#=> "500_MONEY_PIT"
I have a ruby (sinatra) app that I am working on, and my input is a url and if verbose or not (true or false), so basically like this:
The url would look like this: http://localhost:4567/git.company.com&v=false for example.
And the code to fetch those is this:
get '/:url' do |tool_url|
url = params[:url].to_s
is_verbose = params[:v].to_s
I have different classes separated in different files and I'm including them into my main script like this:
Dir["#{File.dirname(__FILE__)}/lib/*.rb"].each { |f| require(f) }
(And a sample file would be something like this), gitlab.rb:
class Gitlab
$gitlab_token = 'TOKEN_GOES_HERE'
def initialize(url, v)
##regex =~ /git.company.com/
##gitlab_url = url
##is_verbose = v
end
def check_gitlab(gitlab_url, is_verbose)
_gitlab_overall = '/health_check.json?token=#{gitlab_token}'
_gitlab_cache = '/health_check/cache.json?token=#{gitlab_token}'
_gitlab_database = '/health_check/database.json?token=#{gitlab_token}'
_gitlab_migrations = '/health_check/migrations.json?token=#{gitlab_token}'
unless is_verbose = true
CheckString.check_string_from_page('https://' + gitlab_url + gitlab_overall, 'success')
else
end
end
end
Now, I want to be able to dynamically know which "class" to use to do a certain job based on the URL that's entered by the user, so my idea was to iterate through those classes looking for a particular variable to match with the input.
I need guidance in this because I've been stuck on this for quite some time now; I've tried so many things that I can think of, but none worked.
Disclaimer: Please bear with me here, because I'm very new to Ruby and I'm not that great in OOP languages (haven't really practiced them that much).
EDIT: I'm open to any suggestion, like if there's a different logic that's better than this, please do let me know.
Make a hash { Regexp ⇒ Class }:
HASH = {
/git.company.com/ => Gitlab,
/github.com/ => Github
}
and then do:
handler = HASH.detect { |k, _| k =~ url }.last.new
The above will give you an instance of the class you wanted.
Sidenotes:
is_verbose = params[:v].to_s always results in is_verbose set to truthy value, check for params[:v].to_s == "true"
is_verbose = true is an assignment, you wanted to use just unless is_verbose.
To make it runtime-resolving, force the plugins to a) include Plugin and b) declare resolve method. Plugin module should define a callback hook:
module Plugin
def self.included(base)
Registry::HASH[-> { base.resolve }] = base
end
end
resolve method should return a regexp, the lambda is here to make it resolved on parsing stage:
class PluginImpl
include Plugin
def resolve
/git.company.com/
end
end
And then match when needed:
handler = HASH.detect { |k, _| k.() =~ url }.last.new
Other way round would be to use ObjectSpace to detect classes, including the module, or declare the TracePoint on base in included callback to provide a direct map, but all this is overcomplicating.
I would like to use something similar to Lodash's get and set, but in Ruby instead of JavaScript. I tried few searches but I can't find anything similar.
Lodash's documentation will probably explain it in a better way, but it's getting and setting a property from a string path ('x[0].y.z' for example). If the full path doesn't exist when setting a property, it is automatically created.
Lodash Set
Lodash Get
I eventually ported Lodash _.set and _.get from JavaScript to Ruby and made a Gem.
Ruby 2.3 introduces the new safe navigator operator for getting nested/chained values:
x[0]&.y&.z #=> result or nil
Otherwise, Rails monkey patches all objects with try(…), allowing you to:
x[0].try(:y).try(:z) #=> result or nil
Setting is a bit harder, and I'd recommend ensuring you have the final object before attempting to set a property, e.g.:
if obj = x[0]&.y&.z
z.name = "Dr Robot"
end
You can use the Rudash Gem that comes with most of the Lodash utilities, and not only the _.get and _.set.
Sometimes I have had the need to programmatically get the value for a property deep into an object, but the thing is that sometimes the property is really a method, and sometimes it needs parameters!
So I came up with this solution, hope it helps devising one for your problem:
(Needs Rails' #try)
def reduce_attributes_for( object, options )
options.reduce( {} ) do |hash, ( attribute, methods )|
hash[attribute] = methods.reduce( object ) { |a, e| a.try!(:send, *e) }
hash
end
end
# Usage example
o = Object.new
attribute_map = {
# same as o.object_id
id: [:object_id],
# same as o.object_id.to_s
id_as_string: [:object_id, :to_s],
# same as o.object_id.to_s.length
id_as_string_length: [:object_id, :to_s, :length],
# I know, this one is a contrived example, but its purpose is
# to illustrate how you would call methods with parameters
# same as o.object_id.to_s.scan(/\d/)[1].to_i
second_number_from_id: [:object_id, :to_s, [:scan, /\d/], [:[],1], :to_i]
}
reduce_attributes_for( o, attribute_map )
# {:id=>47295942175460,
# :id_as_string=>"47295942175460",
# :id_as_string_length=>14,
# :second_number_from_id=>7}
If I have a method like this:
require 'tweetstream'
# client is an instance of TweetStream::Client
# twitter_ids is an array of up to 1000 integers
def add_first_users_to_stream(client, twitter_ids)
# Add the first 100 ids to sitestream.
client.sitestream(twitter_ids.slice!(0,100))
# Add any extra IDs individually.
twitter_ids.each do |id|
client.control.add_user(id)
end
return client
end
I want to use rspec to test that:
client.sitestream is called, with the first 100 Twitter IDs.
client.control.add_user() is called with the remaining IDs.
The second point is trickiest for me -- I can't work out how to stub (or whatever) a method on an object that is itself a property of an object.
(I'm using Tweetstream here, although I expect the answer could be more general. If it helps, client.control would be an instance of TweetStream::SiteStreamClient.)
(I'm also not sure a method like my example is best practice, accepting and returning the client object like that, but I've been trying to break my methods down so that they're more testable.)
This is actually a pretty straightforward situation for RSpec. The following will work, as an example:
describe "add_first_users_to_stream" do
it "should add ids to client" do
bulk_add_limit = 100
twitter_ids = (0..bulk_add_limit+rand(50)).collect { rand(4000) }
extras = twitter_ids[bulk_add_limit..-1]
client = double('client')
expect(client).to receive(:sitestream).with(twitter_ids[0...bulk_add_limit])
client_control = double('client_control')
expect(client).to receive(:control).exactly(extras.length).times.and_return(client_control)
expect(client_control).to receive(:add_user).exactly(extras.length).times.and_return {extras.shift}
add_first_users_to_stream(client, twitter_ids)
end
end
I have gone over the documentation, and I can't find a specific way to go about this. I have already added some dynamic attributes to a model, and I would like to be able to iterate over all of them.
So, for a concrete example:
class Order
include Mongoid::Document
field :status, type: String, default: "pending"
end
And then I do the following:
Order.new(status: "processed", internal_id: "1111")
And later I want to come back and be able to get a list/array of all the dynamic attributes (in this case, "internal_id" is it).
I'm still digging, but I'd love to hear if anyone else has solved this already.
Just include something like this in your model:
module DynamicAttributeSupport
def self.included(base)
base.send :include, InstanceMethods
end
module InstanceMethods
def dynamic_attributes
attributes.keys - _protected_attributes[:default].to_a - fields.keys
end
def static_attributes
fields.keys - dynamic_attributes
end
end
end
and here is a spec to go with it:
require 'spec_helper'
describe "dynamic attributes" do
class DynamicAttributeModel
include Mongoid::Document
include DynamicAttributeSupport
field :defined_field, type: String
end
it "provides dynamic_attribute helper" do
d = DynamicAttributeModel.new(age: 45, defined_field: 'George')
d.dynamic_attributes.should == ['age']
end
it "has static attributes" do
d = DynamicAttributeModel.new(foo: 'bar')
d.static_attributes.should include('defined_field')
d.static_attributes.should_not include('foo')
end
it "allows creation with dynamic attributes" do
d = DynamicAttributeModel.create(age: 99, blood_type: 'A')
d = DynamicAttributeModel.find(d.id)
d.age.should == 99
d.blood_type.should == 'A'
d.dynamic_attributes.should == ['age', 'blood_type']
end
end
this will give you only the dynamic field names for a given record x:
dynamic_attribute_names = x.attributes.keys - x.fields.keys
if you use additional Mongoid features, you need to subtract the fields associated with those features:
e.g. for Mongoid::Versioning :
dynamic_attribute_names = (x.attributes.keys - x.fields.keys) - ['versions']
To get the key/value pairs for only the dynamic attributes:
make sure to clone the result of attributes(), otherwise you modify x !!
attr_hash = x.attributes.clone #### make sure to clone this, otherwise you modify x !!
dyn_attr_hash = attr_hash.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
or in one line:
x.attributes.clone.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
So, what I ended up doing is this. I'm not sure if it's the best way to go about it, but it seems to give me the results I'm looking for.
class Order
def dynamic_attributes
self.attributes.delete_if { |attribute|
self.fields.keys.member? attribute
}
end
end
Attributes appears to be a list of the actual attributes on the object, while fields appears to be a hash of the fields that were predefined. Couldn't exactly find that in the documentation, but I'm going with it for now unless someone else knows of a better way!
try .methods or .instance_variables
Not sure if I liked the clone approach, so I wrote one too. From this you could easily build a hash of the content too. This merely outputs it all the dynamic fields (flat structure)
(d.attributes.keys - d.fields.keys).each {|a| puts "#{a} = #{d[a]}"};
I wasn't able to get any of the above solutions to work (as I didn't want to have to add slabs and slabs of code to each model, and, for some reason, the attributes method does not exist on a model instance, for me. :/), so I decided to write my own helper to do this for me. Please note that this method includes both dynamic and predefined fields.
helpers/mongoid_attribute_helper.rb:
module MongoidAttributeHelper
def self.included(base)
base.extend(AttributeMethods)
end
module AttributeMethods
def get_all_attributes
map = %Q{
function() {
for(var key in this)
{
emit(key, null);
}
}
}
reduce = %Q{
function(key, value) {
return null;
}
}
hashedResults = self.map_reduce(map, reduce).out(inline: true) # Returns an array of Hashes (i.e. {"_id"=>"EmailAddress", "value"=>nil} )
# Build an array of just the "_id"s.
results = Array.new
hashedResults.each do |value|
results << value["_id"]
end
return results
end
end
end
models/user.rb:
class User
include Mongoid::Document
include MongoidAttributeHelper
...
end
Once I've added the aforementioned include (include MongoidAttributeHelper) to each model which I would like to use this method with, I can get a list of all fields using User.get_all_attributes.
Granted, this may not be the most efficient or elegant of methods, but it definitely works. :)