Apologies for the very broadly title question.
Basically this follows on from my earlier question about defs and how they are called on instantiated methods.
Basically the way I have it now:
I set up an active resource on client side and post it with .save
This then goes through my controller on server and stores an active record of the same class.
so MyResource-->save-->MyRecord
The MyRecord is stored with a status column containing a simple string.
Thing is MyRecord class has a def called
def get_status
puts status
end #Amazing method, I know
In my mind If i wanted to execute the get_status on MyRecord, all I had to do was this.
(Please note this is client side)
#test = MyRecord.find(1)
#test.get_status
Sadly this is not the case as #test becomes an active resource and cant call a method it doesnt have.
(NOTE: My classes are not actually called MyRecord and MyResource, They are just title that for simplicity as I'd rather understand the solution than have someone solve it for me.)
Would anyone care to point me in the right direction to explain how I call the active record method from client side. Have I gone completely the wrong way about it and should it be processed in controller instead of model?
On a side note: Are there any alternatives to .save? My boss doesn't like it for reasons I cannot understand. (NOTE: he's a lead, I'm an intern therefore I don't argue or ask questions that seem like a challenge)
Honestly, ActiveResource needs a little work... I had to implement this exact feature where I work and ended up rolling my own semi RPC lib using basic http to share code between client and server.
Really though something like ruby-rpc or DRb would probably be a better option.
You can include the result of ARecord methods in the data shipped to your AResource model:
class MyRecord < ActiveRecord::Base
...
def to_xml(options={})
super(options.merge(:methods => [ :your_method_name_here ]))
end
...
end
You'll need to re-create any other methods in the AResource model that you want to use on the client. (I don't know how a puts belongs in an ARecord model, but do whatever floats your boat.)
Related
I have a monkeypatched of ActiveRecord find with some business logic, for example:
# lib/core_extensions/active_record/finder_methods/finder.rb
module ActiveRecord
module FinderMethods
def find(*args)
return super if block_given?
#... business logic code => my_error_control = true
raise "My Error" if my_error_control
retorn = find_with_ids(*args)
end
end
end
retorn
I have not seen many examples like this, and this causes me a doubt:
Where should finder.rb be?
In this example, this file is in lib/core_extensions/... but if it contains business logic, I think finder.rb should lives in the folder app/core_extensions/ isn't it?
Edited, after Sergio Answer
things like this, are a bad practice?
# lib/core_extensions/nil_class/image_attributes.rb
# suport for product images attributes
class NilClass
def main_image(size,evita_video)
"/images/paperclip_missing/original/missing.png"
end
end
Where should finder.rb be?
Ultimately, it doesn't matter. It only matters that this code gets loaded. This mix of patching base libraries and adding business logic there looks like something that MUST be documented thoroughly (in the project's wiki or something like that). And if it is documented, then it doesn't matter. The code is where the documentation says it is.
That being out of the way, here's a design suggestion:
when user seeks a Family Family.find(params[family_id],session[:company_id]), this find will compare the company of the family result family.company witht the parameter
Why not do something like this:
family = current_company.families.find(params[:family_id])
where current_company can be defined as #current_company ||= Company.find(session[:company_id])
Here, if this company doesn't have this family, you'll get an exception.
Same effect*, only without any patching. Much more futureproof. You can even add a couple of rubocop rules to ensure that you never write a naked Family.find.
* it's not like you add that patch and rest of your code magically acquires super-powers. No. You still have to change all the finders, to pass that company id.
It's the first time I see such case :). I'd put it in app/core_extensions and check if live reloading works correctly with it. If not, I'd move it to lib/. (It's just a heuristic)
Edit:
Instead of extending NilClass I'd rather use regular NullObjects. It's really less surprising and easier to understand.
https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object
I'm working with a massive legacy code base, so I am looking for advice concerning this particular issue, please, not suggestions of better high-level implementations.
A simplified version of what I'm working with:
class Order < ActiveRecord::Base
has_many :line_items
#other stuff
def balance
#some definition
end
end
class LineItem < ActiveRecord::Base
belongs_to :order
#other stuff
end
module Concerns
module LineItems
module Aggregates
extend ActiveSupport::Concern
#stuff
def balance
#some other definition
end
end
end
end
Order has a method called 'balance,' and a module of LineItem also has a method called 'balance.' It seems that most of the time (in most places in the code base), when specific_line_item.balance is called, it used the method definition under the LineItem module, but there are a couple of places where it instead calls the method from Order.
Is there any way in Ruby/Rails to specify on method call which of these two I'd like to use? OR is there probably something else going on here because Ruby doesn't have method overloading, so the problem I'm describing here isn't possible?
All relevant cases where either method is called are coming from a line_item (i.e. specific_line_item.balance), so I would think it would always choose the method closer to home, rather than making the associative jump and calling Order's 'balance' method without being told to.
EDIT:
Thanks for the responses! It seems I wasn't clear enough with my question. I understand the difference between
Order.first.balance
and
LineItem.first.balance
and that the balance method being called is the one defined within the class for that object. In the situation I'm describing, I observed, in the actual live app environment, that at a place in the code where
LineItem.find(some_id).balance
was called it output not the result that would be computed by the LineItem 'balance' method, but the one from the Order class.
So I had hoped to learn that there's some ruby quirk that might have an object call an associate's method of the same name under some conditions, rather than it's own. But I'm thinking that's not possible, so there's probably something else going on under the covers specific to this situation.
Firstly, ActiveRecord::Concern can change a lot of behaviour and you've left out a lot of code, most crucially, I don't know where it's being injected, but I can make an educated guess.
For a Concern's methods to be available a given object, it must be include'd in the object's class's body.
If you have access to an instance of the Order object, at any point you can call the balance method:
order = Orders.last # grab the last order in your database
order.balance # this will call Order#balance
And if you have the Order then you can also get the LineItem:
order.line_items.first.balance # should call the Concerns:: LineItems::Aggregates#balance
You can open up a Rails console (with rails console) and run the above code to see if it works as you expect. You'll need a working database to get meaningful orders and balances, and you might need to poke around to find a completed order, but Ruby is all about exploration and a REPL is the place to go.
I'd also grep (or ag or ack) the codebase looking for calls to balance maybe doing something like grep -r "(^|\s)\w+\.balance" *, what you want to look for is the word before .balance, that is the receiver of the "balance" message, if that receiver is an Order object then it will call Order#balance and if it is a LineItem object then it will call Concerns:: LineItems::Aggregates#balance instead.
I get the feeling you're not familiar with Ruby's paradigm, and if that's the case then an example might help.
Let's define two simple Ruby objects:
class Doorman
def greet
puts "Good day to you sir!"
end
end
class Bartender
def greet
puts "What are you drinking?"
end
end
Doorman and Bartender both have a greet method, and which is called depends on the object we call greet on.
# Here we instantiate one of each
a_doorman = Doorman.new
a_bartender = Bartender.new
a_doorman.greet # outputs "Good day to you sir!"
a_bartender.greet # outputs "What are you drinking?"
We're still using a method called greet but the receiver is what determines which is called.
Ruby is a "message passing language" and each "method" is not a function but it's a message that is passed to an object and handled by that object.
References
How to use concerns in Rails 4
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
http://guides.rubyonrails.org/command_line.html#rails-console
One of our use cases involves publishing active record models over Drb. It looks like when we do this we are inadvertently leaving connections checked out and as a result we're receiving AR timeouts.
I think this is because of this comment in the active record code:
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html
Specifically
"Simply use ActiveRecord::Core#connection as with Active Record 2.1
and earlier (pre-connection-pooling). Eventually, when you're done
with the connection(s) and wish it to be returned to the pool, you
call ActiveRecord::Base.clear_active_connections!. This will be the
default behavior for Active Record when used in conjunction with
Action Pack's request handling cycle."
When we're accessing out models over Drb we're not going through the request cycle so the connection is not getting checked back in.
The same document suggests we need to check these connections back in manually - what I need is a way to hook into all methods on a published model and call "ActiveRecord::Base.clear_active_connections" afterwards.
class Foo < ActiveRecord::Base
#I need this method to be called after every method on this class!
def close_connections
ActiveRecord::Base.close_active_connections
end
end
Closing the connections manually isn't really an option because there are tens of thousands of lines of code and I'd need to go and add "close the connection" after every single one!
You could add this snippet at the end of your class definition..
(instance_methods - Class.new.methods).each do |method|
define_method "#{method}_with_close_connections" do |*args, &block|
self.send "#{method}_without_close_connections", *args, &block
ActiveRecord::Base.close_active_connections
end
alias_method_chain method, :close_connections
end
Highly non-recommended, however. You should probably find another solution.
One potential solution is to use Observers - http://api.rubyonrails.org/v3.2.13/classes/ActiveRecord/Observer.html
You will, however, need an observer for each one of your Models.
Before going down this path though, I would thoroughly evaluate your implementation and find a better way of accessing the connection pool.
I have a basic has_many :through relationship that is bi-directional:
calendars have many calendar_calendar_events
calendars have many events through calendar_calendar_events
events have many calendar_calendar_events
events have many calendars through calendar_calendar_events
I'm wanting to assign calendars to an event with the basic calendar_ids= function that has_many :through sets up, however, I want to override this function to add some extra magic. I've had a look through the rails source and can't find the code for this function. I'm wondering if someone could point me to it. I'll then override it for this class to add the stuff that I want :)
You can find the source code in the file lib/active_record/associations.rb at line 1295
def collection_accessor_methods(reflection, association_proxy_class, writer = true)
collection_reader_method(reflection, association_proxy_class)
if writer
define_method("#{reflection.name}=") do |new_value|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
association = send(reflection.name)
association.replace(new_value)
association
end
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
end
end
end
You should definitely avoid to overwrite such this method to add magic stuff.
Rails is already "too much magic" sometimes. I would suggest to create a virtual attribute with all your custom logic for several reasons:
some other rails methods might rely on the default implementation
you rely on a specific API that might going to change in future ActiveRecord versions
After a bit of a hunt I found it:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/collection_accessor_methods
It didn't look like what I thought it would look like, so that's why I probably missed it. I ended up overriding the calendars= method instead of the calendar_ids= method and everything works well.
In response to the answer above, I used alias_method_chain to override the default setter and add my feature. Works quite well, though I'm not sure why I have to send the method setter instead of just using it normally. It didn't seem to work though so this will do :)
def calendars_with_primary_calendar=(new_calendars)
new_calendars << calendar unless new_record?
send('calendars_without_primary_calendar=', new_calendars) # Not sure why we have to call it this way
end
alias_method_chain :calendars=, :primary_calendar
Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)