Using ActiveRecord with DelegateClass - ruby

Using rails 3.2.8 I have a model setup with a couple attributes
class MyModel < ActiveRecord::Base
attr_accessible :foo, :bar
end
I have another class setup using the above as a delegate class
class MyModelPresenter < DelegateClass(MyModel)
def initialize(month, obj)
#month = month
super(obj)
end
def self.build(month, attributes = { })
new(month, MyModel.new).tap do |p|
p.attributes = attributes
end
end
def attributes=(attributes)
attributes.each { |k, v| send("#{k}=", v) }
end
end
When I create a new MyModelPresenter like so:
MyModelPresenter.build(Date.today, {:foo => 1})
I get the following back
NoMethodError: undefined method `foo=' for #<MyModel:0x1098f31a8>
from /Users/me/.rbenv/versions/ree-1.8.7-2011.03/lib/ruby/gems/1.8/gems/activemodel-3.2.8/lib/active_model/attribute_methods.rb:404:in `method_missing'
from /Users/me/.rbenv/versions/ree-1.8.7-2011.03/lib/ruby/gems/1.8/gems/activerecord-3.2.8/lib/active_record/attribute_methods.rb:149:in `method_missing'
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:23:in `send'
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:23:in `attributes='
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:23:in `each'
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:23:in `attributes='
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:17:in `build'
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:15:in `tap'
from /Users/me/dev/temp/app/presenters/my_model_presenter.rb:15:in `build'
For some reason the database attributes on the model aren't getting defined (setters or getters). Before upgrading to rails 3.2 this was all working in a rails 3.1 app.
Does anyone have any idea why the model's attribute methods aren't getting defined?

Inheriting from Delegator instead of using DelegateClass solved the problem. Here is what the final class looked like:
class MyModelPresenter < Delegator
def initialize(month, obj)
#month = month
super(obj)
#_sd_obj = obj
end
def __getobj__
#_sd_obj
end
def self.build(month, attributes = { })
new(month, MyModel.new).tap do |p|
p.attributes = attributes
end
end
def attributes=(attributes)
attributes.each { |k, v| send("#{k}=", v) }
end
end
As you can see I also had to add the def __getobj__ method. and set #_sd_obj in the initializer to an instance of the class I'm delegating from.
See the SimpleDelegator example.

Related

"Method << does not exist on T.class_of(A)" use of def_delegators with sorbet

I've got a code that uses Singleton and Forwardable to delegate some methods to the internal Hash.
class A
include Singleton
extend T::Sig
sig { returns(T::Hash[T.untyped, T.untyped]) }
attr_reader :stars
class << self
extend Forwardable
def_delegators :instance, :<<, :[], :stars
def_delegators :stars, :length
end
sig { void }
def initialize
#stars = {}
end
sig { params(identifier: String).returns(B) }
def [](identifier)
#stars[identifier]
end
end
I use A << B.new outside A class and when I run be srb tc I get Method << does not exist on T.class_of(A). Any suggestions or ideas how I can get rid of this warning?

Using current_user to customize ActionCable channel submittion

I'm making instant commenting with Rails 5. Using Rails *.js.erb, which is comfortable for this size of project. Also use ActionCable to deliver new comments to users. The thing is that I want ActionCable to send rendered comments/_comment.html.haml directly to client to evaluate this code in browser. In common case it's totally achivable. But in erb template of comment I have to deal with current_user to add delete link if current_user.admin? The problem is when I invoke CommentsChannel.broadcast_to and render _comment.html.haml renderer do not now current_user.
Question is how can I render _comment.html.haml and submit it to each separate subscription defined by current user, using current user from this subscription?
Devise is used for authentication.
comments/_comment.html.haml
%div[comment]
.details
%span.author= comment.user.email
%span.time= l comment.created_at, format: :short
= comment.body
= link_to t('common.reply'), comment, class: 'reply-link'
= link_to t('common.delete'), comment, method: :delete, remote: true if current_user.admin?
= render 'comments/form', comment: comment.commentable.comment_threads.build, parent: comment
- if comment.children.any?
.child-comments
= render comment.children.sort_by { |c| c.created_at }.reverse
application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
def find_verified_user
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
broadcast_comment_job.rb
class BroadcastCommentJob < ApplicationJob
queue_as :default
def perform(comment)
CommentsChannel.broadcast_to \
comment.commentable,
comment: render_comment(comment)
end
private
def render_comment(comment)
CommentsController.render(partial: 'comment', locals: { comment: comment })
end
end
comment.rb
class Comment < ActiveRecord::Base
...
after_create_commit { BroadcastCommentJob.perform_now(self) }
...
end

Ruby Quick Class Instance Scope

I have a quick problem that probably comes down to something stupid. I have a class that extends OAuth::AccessToken and uses instance variables (#) so that each time it constructs an object, those variables will be unique that instance. However, when I try to return the final object from this class, I get an error. A quick example:
require 'oauth'
class OauthFigshare < OAuth::AccessToken
def initialize (consumerkey, consumersecret, accesstoken, accesstokensecret)
#consumerkey = consumerkey
#consumersecret = consumersecret
#accesstoken = accesstoken
#accesstokensecret = accesstokensecret
#apiurl = "http://api.figshare.com"
#consumer = OAuth::Consumer.new(#consumerkey,#consumersecret,{:site=> #apiurl})
#token = { :oauth_token => #accesstoken, :oauth_token_secret => #accesstokensecret}
puts #consumer.class
puts #token
#client = OAuth::AccessToken.from_hash(#consumer, #token)
puts #client
puts #client.get('/v1/my_data/articles')
return #client
end
end
The problem is that when I check inside the class to see if the token is working, it does. However, when I check against the constructed object outside the class, it doesn't work.
#client.get(url) returns Net::HTTPOk calling from in the class
auth = OauthFigshare.new(inputs)
auth.get(url)
This returns Net::HTTPUnauthorized
What am I not getting about scope here?
Edit to include actual class
The return value of the initialize method is not used. It seems like you actually want to override self.new instead.

Is there a better way to test this Ruby class with RSpec?

I'm extracting a subset of fields from a full JSON dataset having a JSON fixture. The better way I could think of is the following :
require "spec_helper"
# API ref.: GET /repos/:owner/:repo
# http://developer.github.com/v3/repos/
describe Elasticrepo::RepoSubset do
context "extract a subset of repository fields" do
let(:parsed) { Yajl::Parser.parse(fixture("repository.json").read) }
subject { Elasticrepo::RepoSubset.new(parsed) }
context "#id" do
its(:id) { should eq(2126244) }
end
context "#owner" do
its(:owner) { should eq("twitter") }
end
context "#name" do
its(:name) { should eq("bootstrap") }
end
context "#url" do
its(:url) { should eq("https://api.github.com/repos/twitter/bootstrap") }
end
context "#description" do
its(:description) { should eq("Sleek, intuitive, and powerful front-end framework for faster and easier web development.") }
end
context "#created_at" do
its(:created_at) { should eq("2011-07-29T21:19:00Z") }
end
context "#pushed_at" do
its(:pushed_at) { should eq("2013-04-13T03:56:36Z") }
end
context "#organization" do
its(:organization) { should eq("Organization") }
end
context "#full_name" do
its(:full_name) { should eq("twitter/bootstrap") }
end
context "#language" do
its(:language) { should eq("JavaScript") }
end
context "#updated_at" do
its(:updated_at) { should eq("2013-04-13T19:12:09Z") }
end
end
end
but I wonder if is there a better, smarter, cleaner or just more elegant way of doing that. The class I TDD out is this :
module Elasticrepo
class RepoSubset
attr_reader :id, :owner, :name, :url, :description, :created_at, :pushed_at,
:organization, :full_name, :language, :updated_at
def initialize(attributes)
#id = attributes["id"]
#owner = attributes["owner"]["login"]
#name = attributes["name"]
#url = attributes["url"]
#description = attributes["description"]
#created_at = attributes["created_at"]
#pushed_at = attributes["pushed_at"]
#organization = attributes["owner"]["type"]
#full_name = attributes["full_name"]
#language = attributes["language"]
#updated_at = attributes["updated_at"]
end
end
end
I would remove the individual context blocks. They don't add any additional information.
I'd use a map of keys/values and iterate, or create an object with the correct values and compare the entire object.

Having trouble with send and define_method

I'm trying to create a custom attr_accessor, but can't seem to get it to work. Instead of returning the value assigned to the writer, it returns the instance variable. Any ideas?
class Object
def custom_attr_accessor(klass, attribute)
ivar = "##{attribute}".to_sym
writer_body = lambda { |arg| instance_variable_set(ivar, arg) }
reader_body = lambda { ivar }
klass.send(:define_method, "#{attribute}=".to_sym, &writer_body)
klass.send(:define_method, "#{attribute}".to_sym, &reader_body)
end
end
class Person
end
custom_attr_accessor(Person, :age)
me = Person.new
me.age = 100
puts me.age
=> #age
Just like you did a instance_variable_set, you need instance_variable_get:
reader_body = lambda { instance_variable_get(ivar) }
BTW, extending Object and passing a class is not very pretty. Try to make it Persion. custom_attr_accessor(:age), that would be much more OOP.

Resources