How to accept an array of key:value arguments in a Thor generator command? - ruby

I am working on creating a generator similar to the Rails scaffolding generator. I would like to accept an array of key:value arguments. Like this:
mycli generate model BlogPost title:string body:text published:datetime
Currently my command class looks something like this:
require "thor"
module Mycli
module Generators
class Model < Thor::Group
include Thor::Actions
argument :model_name
# argument :model_attributes # TODO: figure out how to get array of attributes
def self.source_root
File.dirname(__FILE__)
end
def generate_model
template('templates/model.tt', "app/models/#{model_name}.rb")
end
def generate_migration
template('templates/migration.tt', "migrations/#{model_name}.rb")
end
end
end
end
What do I need to do in order to access that list of attributes?

Looks like this feature is already supported. You just need to specify the argument type as a :hash.
argument :model_attributes, optional: true, type: :hash

Related

Parsed_response in Ruby from HTTP

How can i get parsed_response from here?
require 'HTTParty'
require 'httparty/request'
require 'httparty/response/headers'
class CRUD
include HTTParty
def retrieve
##response = CRUD.get('http://dummy.restapiexample.com/api/v1/employee/id')
end
end
{"id":"719","employee_name":"test","employee_salary":"123","employee_age":"23","profile_image":""}
puts #manter_user.retrieve.parsed_response['employee_name'] -- dont work
puts CRUD.class_variable_get(:##response).parsed_response['employee_name'] -- dont work
It's an instance method, it means that you need to create an instance. And you don't need global variable. And it is bad idea to name class with all uppercase letters - this style is used for constants. Classes and modules use MixedCase and have no underscores, each word starts with an uppercase letter.
class Crud
include HTTParty
def retrieve
self.class.get('http://dummy.restapiexample.com/api/v1/employee/id')
end
end
> Crud.new.retrieve.parsed_response
Since you are getting the JSON response, you can parsed it back as
require 'json'
foo = JSON['{"id":"719","employee_name":"test","employee_salary":"123","employee_age":"23","profile_image":""}']
puts foo['employee_name'] # => test

Handling a method from outside of a mixin module in ruby and rspec

I have a class that looks like this:
module ReusableBitlyLinks
def shorten_url url, *args
ShortenedUrl.shorten_url_with_bitly( url, email.user )
end
end
I have a test that looks like this:
require File.expand_path("../../../../app/decorators/mixins/reusable_bitly_links", __FILE__)
include ReusableBitlyLinks
describe ReusableBitlyLinks do
describe "shorten_url" do
it "works" do
ReusableBitlyLinks.shorten_url('asdf').should == 'asdf'
end
end
end
When I run the test I get an error that says:
uninitialized constant ReusableBitlyLinks::ShortenedUrl
How do I mock stub ReusableBitlyLinks::ShortenedUrl?
Is ShortenedUrl defined inside ReusableBitlyLinks module? If not - try to access it with ::ShortenedUrl.shorten_url_with_bitly.
Not sure what stubbing has to do with it...
In your mixin module, you need to tell it that ShortenedUrl is from outside the module by prepending :::
module ReusableBitlyLinks
def shorten_url(url, *args)
::ShortenedUrl.shorten_url_with_bitly(url, email.user)
end
end
You may also need to do a require inside the mixin file to load whatever file it is that defines ShortenedUrl.
Further, in your test file, the line:
require File.expand_path("../../../../app/decorators/mixins/reusable_bitly_links", __FILE__)
could be simplified to:
require_relative '../../../../app/decorators/mixins/reusable_bitly_links'

Change class instance variable from a module

I have several classes (Elves, Dwarves, Giants, etc.), and I would like to add "powers" to them via a powerable module that lets me add several fields/methods to a model (I am using Mongoid just for the sake of example) using a simple DSL has_superpower(power_name)
module Powerable::Elf
extend ActiveSupport::Concern
include Powerable::Mongoid
has_superpower(:super_hearing)
has_superpower(:night_vision)
end
class Elf
include Powerable::Elf
end
Each time I call the class method has_superpower(power_name), it should register the power_name somewhere so I can later get the list of all added powers on a class (and reflect on those names to call other methods).
I don't know how to do that. I had a look at class instance variables or class variables but I'm not sure how to use them for this context
Consider the following boilerplate
module Powerable
module Mongoid
extend ActiveSupport::Concern
def use_power!(power_name, options = {})
send(:"last_#{power_name}_used_at=", Time.now)
...
# Log the power use to the :#{power_name}_uses array
push(#{power_name}_uses, power: power_name, time: Time.now, **options)
end
class_methods do
def has_superpower(power_name)
field :"last_#{power_name}_used_at", type: DateTime
field :"last_#{power_name}_count", type: Integer
field :"#{power_name}_uses", type: Array
end
end
end
Now I would like to define a method that gives me all the uses of all superpowers. For this I would need to register the powers one has, so I can resolve the model fields I need to look at
# I am adding the following to the above module Powerable::Mongoid
module Powerable
module Mongoid
def power_uses(power_name = nil)
if power_name.blank? # Retrieve all power uses)
self.class.all_powers.flat_map do |power_name|# đź‘€ I do not know how to code all_powers
send(:"#{power_name}_uses")
end
else
send(:"#{power_name}_uses")
end
end
end
end
The idea
elf = Elf.new
elf.use_power!(:night_vision, moon: :crescent)
elf.use_power!(:super_hearing, distance: 1.kilometer)
elf.power_uses # => [
power: night_vision, time: xxx, moon: :crescent,
power: super_hearing, time: xxx, distance: 1.kilometer
]

data_mapper, attr_accessor, & serialization only serializing properties not attr_accessor attributes

I'm using data_mapper/sinatra and trying to create some attributes with attr_accessor. The following example code:
require 'json'
class Person
include DataMapper::Resource
property :id, Serial
property :first_name, String
attr_accessor :last_name
end
ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json
produces this output:
"{\"id\":null,\"first_name\":\"Mike\"}"
Obviously I would like for it to give me both the first and last name attributes. Any ideas on how to get this to work in the way one would expect so that my json has all of the attributes?
Also, feel free to also explain why my expectation (that I'd get all of the attributes) is incorrect. I'm guessing some internal list of attributes isn't getting the attr_accessor instance variables added to it or something. But even so, why?
Datamapper has it’s own serialization library, dm-serializer, that provides a to_json method for any Datamapper resource. If you require Datamapper with require 'data_mapper' in your code, you are using the data_mapper meta-gem that requires dm-serializer as part of it’s set up.
The to_json method provided by dm-serializer only serializes the Datamapper properties of your object (i.e. those you’ve specified with property) and not the “normal” properties (that you’ve defined with attr_accessor). This is why you get id and first_name but not last_name.
In order to avoid using dm-serializer you need to explicitly require those libraries you need, rather than rely on data_mapper. You will need at least dm-core and maybe others.
The “normal” json library doesn’t include any attributes in the default to_json call on an object, it just uses the objects to_s method. So in this case, if you replace require 'data_mapper' with require 'dm-core', you will get something like "\"#<Person:0x000001013a0320>\"".
To create json representations of your own objects you need to create your own to_json method. A simple example would be to just hard code the attributes you want in the json:
def to_json
{:id => id, :first_name => first_name, :last_name => last_name}.to_json
end
You could create a method that looks at the attributes and properties of the object and create the appropriate json from that instead of hardcoding them this way.
Note that if you create your own to_json method you could still call require 'data_mapper', your to_json will replace the one provided by dm-serializer. In fact dm-serializer also adds an as_json method that you could use to create the combined to_json method, e.g.:
def to_json
as_json.merge({:last_name => last_name}).to_json
end
Thanks to Matt I did some digging and found the :method param for dm-serializer's to_json method. Their to_json method was pretty decent and was basically just a wrapper for an as_json helper method so I overwrote it by just adding a few lines:
if options[:include_attributes]
options[:methods] = [] if options[:methods].nil?
options[:methods].concat(model.attributes).uniq!
end
The completed method override looks like:
module DataMapper
module Serializer
def to_json(*args)
options = args.first
options = {} unless options.kind_of?(Hash)
if options[:include_attributes]
options[:methods] = [] if options[:methods].nil?
options[:methods].concat(model.attributes).uniq!
end
result = as_json(options)
# default to making JSON
if options.fetch(:to_json, true)
MultiJson.dump(result)
else
result
end
end
end
end
This works along with an attributes method I added to a base module I use with my models. The relevant section is below:
module Base
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def attr_accessor(*vars)
#attributes ||= []
#attributes.concat vars
super(*vars)
end
def attributes
#attributes || []
end
end
def attributes
self.class.attributes
end
end
now my original example:
require 'json'
class Person
include DataMapper::Resource
include Base
property :id, Serial
property :first_name, String
attr_accessor :last_name
end
ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json :include_attributes => true
Works as expected, with the new option parameter.
What I could have done to selectively get the attributes I wanted without having to do the extra work was to just pass the attribute names into the :methods param.
p ps.to_json :methods => [:last_name]
Or, since I already had my Base class:
p ps.to_json :methods => Person.attributes
Now I just need to figure out how I want to support collections.

Should the Applicant class "require 'mad_skills'" or "include 'mad_skills'"?

Also, what does "self.send attr" do? Is attr assumed to be a private instance variable of the ActiveEngineer class? Are there any other issues with this code in terms of Ruby logic?
class Applicant < ActiveEngineer
require 'ruby'
require 'mad_skills'
require 'oo_design'
require 'mysql'
validates :bachelors_degree
def qualified?
[:smart, :highly_productive, :curious, :driven, :team_player ].all? do
|attr|
self.send attr
end
end
end
class Employer
include TopTalent
has_millions :subscribers, :include=>:mostly_women
has_many :profits, :revenue
has_many :recent_press, :through=>[:today_show, :good_morning_america,
:new_york_times, :oprah_magazine]
belongs_to :south_park_sf
has_many :employees, :limit=>10
def apply(you)
unless you.build_successful_startups
raise "Not wanted"
end
unless you.enjoy_working_at_scale
raise "Don't bother"
end
end
def work
with small_team do
our_offerings.extend you
subscribers.send :thrill
[:scaling, :recommendation_engines, : ].each do |challenge|
assert intellectual_challenges.include? challenge
end
%w(analytics ui collaborative_filtering scraping).each{|task|
task.build }
end
end
end
def to_apply
include CoverLetter
include Resume
end
require 'mad_skills' loads the code in mad_skills.rb (or it loads mad_skills.so/.dll depending on which one exists). You need to require a file before being able to use classes, methods etc. defined in that file (though in rails files are automatically loaded when trying to access classes that have the same name as the file). Putting require inside a class definition, does not change its behaviour at all (i.e. putting it at the top of the file would not make a difference).
include MadSkills takes the module MadSkills and includes it into Applicant's inheritance chain, i.e. it makes all the methods in MadSkills available to instances of Applicant.
self.send attr executes the method with the name specified in attr on self and returns its return value. E.g. attr = "hello"; self.send(attr) will be the same as self.hello. In this case it executes the methods smart, highly_productive, curious, driven, and team_player and checks that all of them return true.

Resources