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.
Related
I've written some RSpec tests that successfully create objects with :let statements. However, the test environment doesn't maintain the associations that function properly everywhere else. Below is an example of a class that would turn up a NoMethodError (undefined method `money' for nil:NilClass). Money is a column in Inventory. Any thoughts?
class Inventory < ActiveRecord::Base
belongs_to :character
def self.return_money(character)
character.inventory.money
end
end
And here's a corresponding example for a spec doc:
require 'spec_helper'
describe 'Test methods' do
let(:trader) {
Character.create(
name: "Trader",
location_id: 1)
}
let(:trader_inventory) {
Inventory.create(
character_id: trader.id,
storage_capacity: 50000,
money: 20000,
markup: 1.35)
}
it "test method" do
expect(Inventory.return_money(trader)).to eq(100)
end
end
There is no reason this shouldn't work. RSpec isn't special, it's just regular Ruby code. You can confirm this by moving all of your code into a single file, something like:
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
class Inventory < ActiveRecord::Base
end
class Character < ActiveRecord::Base
has_one :inventory
end
describe 'test' do
it 'works' do
puts Character.first.inventory.money.inspect
end
end
Guesses as to what may be broken:
money is a composite field or something like that. Can you post your database schema?
Library files aren't being loaded correctly. Use puts $LOADED_FEATURES to verify that all the files that should be required have been.
I have the following code (simplified):
decorator.rb
require 'decoratable'
class Decorator < SimpleDelegator
include Decoratable
end
decoratable.rb
require 'decorator_builder'
module Decoratable
def decorate(*decorators)
decorators.inject(DecoratorBuilder.new(self)) do |builder, decorator|
builder.public_send(decorator)
end.build
end
end
decorator_builder.rb
require 'rare_decorator'
class DecoratorBuilder
def initialize(card)
#card = card
#decorators = []
end
def rare
#decorators << ->(card) { RareDecorator.new(card) }
self
end
def build
#decorators.inject(#card) do |card, decorator|
decorator.call(card)
end
end
end
rare_decorator.rb
require 'decorator'
class RareDecorator < Decorator
# Stuff here
end
When I require decorator.rb, it causes RareDecorator to be declared before Decorator is declared, which is a problem since RareDecorator inherits from Decorator.
A possible solution is to split up decorator.rb like so:
class Decorator < SimpleDelegator; end
require 'decoratable'
class Decorator
include Decoratable
end
However, declaring dependencies in the middle of a file doesn't seem doesn't seem like a very clean solution to me.
Is there a better solution to this problem?
Instead of specifying requirements within every file, create one file which will require all the the application's requirements. Call it for example environment.rb:
require 'decoratable'
require 'decorator'
require 'decorator_builder'
require 'rare_decorator'
You don't need to worry about Decoratable not knowing what DecoratorBuilder is, as it is used within the method and the check for the constant will be executed when this method is called. Since you require decorator moment later, all will work.
I'm working on a project to recreate some of the functionality of ActiveRecord. Here's the portion that isn't working
module Associations
def belongs_to(name, params)
self.class.send(:define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
self.class.send(:define_method, :other_table_name) do |other_class|
other_class.table_name
end
.
.
.
o_c = other_class(name, params)
#puts this and other (working) values in a query
query = <<-SQL
...
SQL
#sends it off with db.execute(query)...
I'm building towards this testing file:
require 'all_files' #holds SQLClass & others
pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)
#class Person
#end
class Pet < SQLClass
set_table_name("pets")
set_attrs(:id, :name, :owner_id)
belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end
class Person < SQLClass
set_table_name("people")
set_attrs(:id, :name)
has_many :pets, :foreign_key => :owner_id
end
.
.
.
Without any changes I received
.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)
Just to make sure that it was an issue with the order of loading the classes in the file I began the file with the empty Person class, which, as predicted gave me
undefined method `table_name' for Person:Class (NoMethodError)
Since this is a learning project I don't want to change the test to make my code work (open all the classes, set all the tables/attributes then reopen them them for belongs_to. But, I'm stuck on how else to proceed.)
EDIT SQLClass:
class SQLClass < AssignmentClass
extend SearchMod
extend Associations
def self.set_table_name(table_name)
#table_name = table_name
end
def self.table_name
#table_name
end
#some more methods for finding rows, and creating new rows in existing tables
And the relevant part of AssignmentClass uses send on attr_accessor to give functionality to set_attrs and makes sure that before you initialize a new instance of a class all the names match what was set using set_attrs.
This highlights an important difference between dynamic, interpreted Ruby (et al) and static, compiled languages like Java/C#/C++. In Java, the compiler runs over all your source files, finds all the class/method definitions, and matches them up with usages. Ruby doesn't work like this -- a class "comes into existence" after executing its class block. Before that, the Ruby interpreter doesn't know anything about it.
In your test file, you define Pet first. Within the definition of Pet, you have belongs_to :person. belongs_to does :person.constantize, attempting to get the class object for Person. But Person doesn't exist yet! Its definition comes later in the test file.
There are a couple ways I can think that you could try to resolve this:
One would be to do what Rails does: define each class in its own file, and make the file names conform to some convention. Override constant_missing, and make it automatically load the file which defines the missing class. This will make load order problems resolve themselves automatically.
Another solution would be to make belongs_to lazy. Rather than looking up the Person class object immediately, it could just record the fact that there is an association between Pet and Person. When someone tries to call pet.person, use a missing_method hook to actually define the method. (Presumably, by that time all the class definitions will have been executed.)
Another way would be do something like:
define_method(belongs_to) do
belongs_to_class = belongs_to.constantize
self.class.send(:define_method, belongs_to) do
# put actual definition here
end
self.send(belongs_to)
end
This code is not tested, it's just to give you an idea! Though it's a pretty mind-bending idea, perhaps. Basically, you define a method which redefines itself the first time it is called. Just like using method_missing, this allows you to delay the class lookup until the first time the method is actually used.
If I can say one more thing: though you say you don't want to "overload" method_missing, I don't think that's as much of a problem as you think. It's just a matter of extracting code into helper methods to keep the definition of method_missing manageable. Maybe something like:
def method_missing(name,*a,&b)
if has_belongs_to_association?(name)
invoke_belongs_to_association(name,a,b)
elsif has_has_many_association?(name)
invoke_has_many_association(name,a,b)
# more...
else
super
end
end
Progress! Inspired by Alex D's suggestion to use method_missing to delay the creation I instead used define_methodto create a method for the name, like so:
define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
define_method(:other_table_name) do |other_class|
other_class.table_name
end
#etc
define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
#p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an #params class instance variable and a getter for it,
#but nothing that should make params require an argument
f_k = foreign_key(name, params)
p f_k
o_c = other_class(name, params)
o_t_n = other_table_name(o_c)
p_k = primary_key(params)
query = <<-SQL
SELECT *
FROM #{o_t_n}
WHERE #{p_k} = ?
SQL
row = DBConnection.execute(query, self.send(f_k))
o_c.parse_all(row)
end
I am trying to use some functionality in ActiveModel but I'm having trouble making everything work. I've included my class file and the test I'm running.
The test is failing with:
': undefined method `attr_accessible
I really don't know why, since MassAssignmentSecurity will bring that in and it is in fact running. I've also tried to include all of ActiveModel as well but that's doesn't work either. It doesn't seem to matter if I use include or extend to bring in the MassAssignmentSecurity.
If I pass in some attributes in my test to exercise "assign_attributes" in the initialize, that fails as well. I'm fairly new to rails, so I'm hoping I'm just missing something really simple.
TIA.
Using rails 3.2.12
my_class.rb
class MyClass
include ActiveModel::MassAssignmentSecurity
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
extend ActiveSupport::Callbacks
attr_accessible :persisted, :creds
def initialize(attributes = nil, options = {})
#persisted = false
assign_attributes(attributes, options) if attributes
yield self if block_given?
end
end
my_class_spec.rb
require 'spec_helper'
describe MyClass do
before do
#testcase = MyClass.new
end
subject { #testcase }
it_should_behave_like "ActiveModel"
it { MyClass.should include(ActiveModel::MassAssignmentSecurity) }
it { should respond_to(:persisted) }
end
support/active_model.rb
shared_examples_for "ActiveModel" do
include ActiveModel::Lint::Tests
# to_s is to support ruby-1.9
ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
example m.gsub('_',' ') do
send m
end
end
def model
subject
end
end
Yikes! What a mess I was yesterday. Might as well answer my own question since I figured out my issues.
attr_accessible in MassAssignmentSecurity does not work like it does with ActiveRecord. It does not create getters and setters. You still have to use attr_accessor if you those created.
assign_attributes is a connivence function that someone wrote to wrap around mass_assignment_sanitizer and isn't something baked into in MassAssignment Security. An example implementation is below:
def assign_attributes(values, options = {})
sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
send("#{k}=", v)
end
end
I am trying to understand how the following code is able to do this:
attr_accessor *Configuration::VALID_CONFIG_KEYS
Without requiring the Configuration file. Here is part of the code:
require 'openamplify/analysis/context'
require 'openamplify/connection'
require 'openamplify/request'
module OpenAmplify
# Provides access to the OpenAmplify API http://portaltnx20.openamplify.com/AmplifyWeb_v20/
#
# Basic usage of the library is to call supported methods via the Client class.
#
# text = "After getting the MX1000 laser mouse and the Z-5500 speakers i fell in love with logitech"
# OpenAmplify::Client.new.amplify(text)
class Client
include OpenAmplify::Connection
include OpenAmplify::Request
attr_accessor *Configuration::VALID_CONFIG_KEYS
def initialize(options={})
merged_options = OpenAmplify.options.merge(options)
Configuration::VALID_CONFIG_KEYS.each do |key|
send("#{key}=", merged_options[key])
end
end
....
end
And this is the Configuration module:
require 'openamplify/version'
# TODO: output_format, analysis, scoring can be specied in the client and becomes the default unless overriden
module OpenAmplify
# Defines constants and methods for configuring a client
module Configuration
VALID_CONNECTION_KEYS = [:endpoint, :user_agent, :method, :adapter].freeze
VALID_OPTIONS_KEYS = [:api_key, :analysis, :output_format, :scoring].freeze
VALID_CONFIG_KEYS = VALID_CONNECTION_KEYS + VALID_OPTIONS_KEYS
DEFAULT_ENDPOINT = 'http://portaltnx20.openamplify.com/AmplifyWeb_v21/AmplifyThis'
DEFAULT_HTTP_METHOD = :get
DEFAULT_HTTP_ADAPTER = :net_http
DEFAULT_USER_AGENT = "OpenAmplify Ruby Gem #{OpenAmplify::VERSION}".freeze
DEFAULT_API_KEY = nil
DEFAULT_ANALYSIS = :all
DEFAULT_OUTPUT_FORMAT = :xml
DEFAULT_SCORING = :standard
DEFAULT_SOURCE_URL = nil
DEFAULT_INPUT_TEXT = nil
attr_accessor *VALID_CONFIG_KEYS
....
end
This is from this repository: OpenAmplify
First of all, in both configuration.rb and client.rb, they're using the same naming space, which is module OpenAmplify.
Even though configuration.rb is not required in client.rb, the convention of Ruby project usually requires all necessary files in one file (normally the same name as the name space, and placed in {ProjectName}/lib/, in this case the file is openamplify/lib/openamplify.rb).
So if you go to openamplify/lib/openamplify.rb, you'll notice it actually requires all those two files:
require 'openamplify/configuration'
require 'openamplify/client'
And since constants are already defined in configuration.rb:
module OpenAmplify
module Configuration
VALID_CONFIG_KEYS = ...
end
end
Then obviously constant VALID_CONFIG_KEYS is visible in the same module (re-opened by client.rb) by Configuration::VALID_CONFIG_KEYS (and the * in front just means exploding array, because VALID_CONFIG_KEYS is an array of symbols)
module OpenAmplify
class Client
attr_accessor *Configuration::VALID_CONFIG_KEYS
end
end