active-hash / active_hash

A readonly ActiveRecord-esque base class that lets you use a hash, a Yaml file or a custom file as the datasource
MIT License
1.21k stars 179 forks source link

Could the file lookup path in ActiveYaml become a little more abstract? #325

Closed walterdavis closed 2 weeks ago

walterdavis commented 4 weeks ago

While working on a gem that includes ActiveHash/Yaml as its data store, I wanted to be able to allow the consumer of my gem to override a default YAML file bundled within the gem, or keep on using the default if that's their preference. Because of the way that the file load is anchored to Dir.pwd (which in a Rails app becomes the app root directory), I couldn't work out a way to do this that didn't involve a generator to move the default data file into the parent app, rather than the customary "closest file wins" behavior that the load path usually exhibits.

I don't want to overload the file resolution code in ActiveYaml because I fear that may be a private API you could change at any time. Any suggestions where to look for a way out of this?

Thanks. And seriously, this is an excellent example of well-written code that does what it says on the tin.

flavorjones commented 3 weeks ago

@walterdavis Thanks for asking this question. I think we're open to making this more flexible if it would be helpful, but without more information about the problem you're trying to solve it's difficult for me to suggest an implementation.

Would you like to suggest something? Or share some code that demonstrates the problem you're having?

Specifically, I'm interested in knowing why ActiveYaml::Base.set_root_path doesn't allow you to work around this in the downstream library.

walterdavis commented 2 weeks ago

Sorry for the delay getting back to you about this. Here's what I'm trying to accomplish, and I may just be missing something in my own code, rather than there being anything missing in ActiveHash.

This gem (the one I wrote) contains a default "org tree" of the University of Pennsylvania. It's a single AH-backed model called Org, and each org knows it's parent (through a parent_id) and thus can also find its children.

class Org < ActiveYaml::Base
  set_root_path 'config'
  set_filename 'orgs'
  fields :parent_id, :name, :kind # (and others)

  include ActiveHash::Associations

  has_many :children, class_name: 'Org', foreign_key: :parent_id
  belongs_to :parent, class_name: 'Org'

  # etc.
end

I want to have all the orgs in a yaml file in the gem bundle, and I would also like to be able to "shadow over" that default file by adding a version of it in the Rails app that uses the gem, if needed. (Say, if the orgs need to be updated without revving the gem.)

What I found when trying to set this up was that I could not come up with a location within the gem for the yaml file that was also in the parent app's lookup path, such that the bundled version would be the last-ditch fallback, but anything more specific in the parent app would be preferred if it existed.

I know this is a feature I have seen in other gems, and I thought that it was pretty much something that railties gave you for free, as long as you put things in the correct nesting. (To that point, I have not included the module in my example code above, but I did have a properly nested mountable engine style gem as my test bed for this concept.)

What I ended up doing in the short term was writing an installer that would put the file into the parent app's config folder, and that got me to a working prototype. But I would still like to do this the way I wanted to initially.

If you have any suggestions, or if I haven't explained my problem thoroughly enough, please let me know.

Thanks as always,

Walter

On Oct 27, 2024, at 1:43 PM, Mike Dalessio @.***> wrote:

@walterdavis Thanks for asking this question. I think we're open to making this more flexible if it would be helpful, but without more information about the problem you're trying to solve it's difficult for me to suggest an implementation.

Would you like to suggest something? Or share some code that demonstrates the problem you're having?

Specifically, I'm interested in knowing why ActiveYaml::Base.set_root_path doesn't allow you to work around this in the downstream library.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

flavorjones commented 2 weeks ago

@walterdavis Thanks for the thoughtful reply.

I know this is a feature I have seen in other gems

Can you give me a specific example? The only thing I can think of that does something like this is overriding Rails generator templates. In that case, the generator is carefully configured to look in a single place in a Rails app that matches the location of the template.

Equivalently, then, I would suggest your gem could do something similar:

class Org < ActiveYaml::Base
  if Rails.root.present? && File.exist?(Rails.root.join("config/orgs.yml"))
    set_root_path Rails.root.join("config")
  else
    set_root_path 'config'
  end
  # ...
end

Would that work for you?

walterdavis commented 2 weeks ago

Thanks, that's an interesting idea. I will give that a try. I'll also try to find a specific example of what I'm describing.

Perhaps it is more along the lines of how the view lookup paths work, where a resource can come from the app, or its vendor directory, or the combined app and vendor directories of any gems it bundles that include the Engine class. The effect I have noticed there is that if you have a "shadow" version of a gem's file in your app (including the gem module name in its path) then that file in your app is more authoritative than the one in the gem. It's effectively like you're monkey-patching that file. I've exploited that effect from time to time, when I needed to override something very specific.

Come to think of it, Devise views work this way. Also Kaminari pagination. You can just install the gem and get the vanilla view templates and partials, or you can use the gem's generator (or just write the files, if you know their names) to add the gem's assets to your app's views directory, and modify them there.

Walter

On Nov 2, 2024, at 5:40 PM, Mike Dalessio @.***> wrote:

@walterdavis Thanks for the thoughtful reply.

I know this is a feature I have seen in other gems

Can you give me a specific example? The only thing I can think of that does something like this is overriding Rails generator templates. In that case, the generator is carefully configured to look in a single place in a Rails app that matches the location of the template.

Equivalently, then, I would suggest your gem could do something similar:

class Org < ActiveYaml::Base

if Rails.root.present? && File.exist?(Rails.root.join("config/orgs.yml"))

set_root_path Rails.root.join("config")

else

set_root_path 'config'

end

...

end Would that work for you?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

flavorjones commented 2 weeks ago

The point I'm trying to make in my reply above is that I believe path lookup logic belongs in your gem and not Active Hash.

If there is some primitive that you absolutely believe Active Hash should implement, please let me know -- but simply overriding a yaml file load path that's specific to your gem seems like a gem concern and not an Active Hash concern.

walterdavis commented 2 weeks ago

Understood. Thanks very much.

On Nov 3, 2024, at 12:09 PM, Mike Dalessio @.***> wrote:

The point I'm trying to make in my reply above is that I believe path lookup logic belongs in your gem and not Active Hash.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.