Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scoping resources #43

Open
ianks opened this issue Feb 18, 2020 · 5 comments
Open

Scoping resources #43

ianks opened this issue Feb 18, 2020 · 5 comments
Labels

Comments

@ianks
Copy link

ianks commented Feb 18, 2020

Is it possible to scope resources for viewing? This way, we could scope relations for a given user. Something like:

class Post::Abilities
  include Kan::Abilities

  register_scope('index') do |user|
    if user.admin?
      posts_repo
    else
      posts_repo.exclude(draft: true)
    end
  end
end

Is it possible to do this already?

@davydovanton
Copy link
Owner

hey! I don't have any register_scope methods right now and I'm not sure that this logic should be in the core (but I can imagine a scope plugin for kan).

If you have a simple example with the repository and just one call (user.admin?) I can suggest to use the simplest logic:

class PostRepository
  def all_for_user(user)
    if user.admin?
      self
    else
      exclude(draft: true)
    end
  end
end

But if you want to use resource idea with kan I think you can do it in this way:

module Post
  class BaseResources
    include Kan::Abilities

    role(:base) { |_user, _| true }

    register(:index) { |_user, repo| repo }
  end

  class AdminResources
    include Kan::Abilities

    role(:admin) { |user, _| user.admin? }

    register(:index) { |_user, repo| repo.exclude(draft: true) }
  end
end
resources = Kan::Application.new(
  post: [Post::BaseResources.new, Post::AdminResources.new]
)

ResourcesImport = Dry::AutoInject(resources)
class Service
  include ResourcesImport[resource: 'post.index']
  include Import['post_repo']

  def call(user, params)
    resource.call(user, post_repo) #=> relation with condition
    # ...
  end
end

@davydovanton
Copy link
Owner

davydovanton commented Mar 2, 2020

Also, I get an idea to introduce data_source. I see it like this (it's just a draft to explain my idea):

module Post
  class BaseDataSource
    include Kan::DataSource

    role(:base) { |_user, _| true }

   register(:index) { |_user, repo| repo }
  end

  class AdminDataSource
    include Kan::DataSource

    role(:admin) { |user, _| user.admin? }

    register(:index) { |_user, repo| repo.exclude(draft: true) }
  end
end
app = Kan::Application.new(
  data_sources: { post: [Post::BaseDataSource.new, Post::AdminDataSource.new] }
)

KanImport = Dry::AutoInject(app)
class Service
  include KanImport[data_source: 'data_source.post.index']
  include Import['post_repo']

  def call(user, params)
    source = data_source.call(user, post_repo) #=> relation with condition

    # ...
  end
end

In this case, you can get a specific repository or data source based on your role and use it for getting or setting data. WDYT?

@DangerDawson
Copy link

@davydovanton we are looking at using the above approach, although we are unsure of who this would be used with something like rom-rb / hanami-model. As we currently only return data back from our repository methods and do not leak the relation, e.g. we only return a boolean, single entity, or a collection of entities.

@DangerDawson
Copy link

After giving this some thought, I see 3 separate solutions if you are using the repository pattern:

  • You return the repository method e.g.

register(:index) { |_user| :exclude_draft }

  • you return a set of conditions in a data structure that the repository method can understand e.g.

register(:index) { |_user| { draft: false } }

Additionally wrapping this up in a Dry::Monad::Result could give you the ability to check success or failure, and on success retrieve the conditions / method e.g.

register(:index) { |user| ( Success( { draft: false } ) }

@ianks
Copy link
Author

ianks commented Mar 30, 2021

Also, I get an idea to introduce data_source. I see it like this (it's just a draft to explain my idea):

module Post
  class BaseDataSource
    include Kan::DataSource

    role(:base) { |_user, _| true }

   register(:index) { |_user, repo| repo }
  end

  class AdminDataSource
    include Kan::DataSource

    role(:admin) { |user, _| user.admin? }

    register(:index) { |_user, repo| repo.exclude(draft: true) }
  end
end
app = Kan::Application.new(
  data_sources: { post: [Post::BaseDataSource.new, Post::AdminDataSource.new] }
)

KanImport = Dry::AutoInject(app)
class Service
  include KanImport[data_source: 'data_source.post.index']
  include Import['post_repo']

  def call(user, params)
    source = data_source.call(user, post_repo) #=> relation with condition

    # ...
  end
end

In this case, you can get a specific repository or data source based on your role and use it for getting or setting data. WDYT?

I really dig this pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants