Create a Smart relationship (Rails)

Feature(s) impacted

forest_liana (7.7.3)
rails (7.0.2.4)

Observed behavior

Missing ‘/relationships/’ from route

Expected behavior

Failure Logs

Started GET "/forest/User/175/all_cards?timezone=Europe%2FSamara&fields%5BCard%5D=card_type%2....mber%5D=1&page%5Bsize%5D=15&sort=-id" for 127.0.0.1 at 2023-01-10 14:52:39 -0500
Started GET "/" for 127.0.0.1 at 2023-01-10 14:52:40 -0500
Processing by HomeController#show as JSON
  Parameters: {"home"=>{}}
Completed 500 Internal Server Error in 14ms (ActiveRecord: 0.0ms | MongoDB: 0.0ms | Allocations: 5181)

## Correct behavior 

Started GET “/forest/User/175/relationships/user_profiles/count?fields%5BUserProfile%5D=user%2…system_role%5D=name&fields%5Bproject%5D=name&timezone=Europe%2FSamara” for 127.0.0.1 at 2023-01-10 14:52:28 -0500
Processing by ForestLiana::AssociationsController#index as JSON
Processing by ForestLiana::AssociationsController#count as JSON

Context

  • Project name: …
  • Team name: …
  • Environment name: …
  • Agent type & version: …
  • Recent changes made on your end if any: …

Hello,

I am trying to implement a simple Smart Relationship by following this guide:

User has_many UserProfiles
UserProfile has_many Cards

I am trying to add ‘All Cards’ in the Users collection Related Data:

collections/user.rb:

  has_many :all_cards, type: ['String'], reference: 'Card.id'

routes:

    get '/User/:user_id/relationships/all_cards' => 'users#all_cards'

users_controller:

  def all_cards
    limit = params['page']['size'].to_i
    offset = (params['page']['number'].to_i - 1) * limit
    user = User.find(user_id)
    user_profiles = user.user_profiles
    cards = user_profiles.limit(limit).offset(offset).map(&:cards)
    count = cards.count

    render json: serialize_models(cards, meta: {count: count})    
end

But fails with:

Started GET "/forest/User/175/all_cards?timezone=Europe%2FSamara&fields%5BCard%5D=card_type%2....mber%5D=1&page%5Bsize%5D=15&sort=-id" for 127.0.0.1 at 2023-01-10 14:52:39 -0500
Started GET "/" for 127.0.0.1 at 2023-01-10 14:52:40 -0500
Processing by HomeController#show as JSON
  Parameters: {"home"=>{}}
Completed 500 Internal Server Error in 14ms (ActiveRecord: 0.0ms | MongoDB: 0.0ms | Allocations: 5181)

What am I missing/doing wrong?

Thanks!

Hello @muz,

From what I can quickly see in your failure logs, the request was sent to ‘forest/User/:user_id/all_cards’ instead of ‘forest/User/:user_id/relationships/all_cards’.

Best regards,

Dogan

Hello @dogan.ay,

Exactly! That is what I am trying to understand - why is ‘relationships/’ being omitted?

What in my above shown code is causing that?

Thanks for the reply!

I’m sorry, I didn’t understand your issue at first. Can you confirm me that your endpoint ‘forest/User/:user_id/relationships/all_cards’ is working as it should and the issue is that the query sent by your browser is made to the following url: 'forest/User/:user_id/all_cards’ ?

Would you please share with me the name of your project and the environment on which you are working ?

Thanks again @dogan.ay

Project:
Environment: Development

It doesn’t look like the route is working properly. Here is a users routes excerpt :

$ rails routes -c users
...

             					forest GET    /forest/User/:user_id/relationships/all_cards(.:format)      forest/users#all_cards
...

We’re looking into the issue and trying to reproduce the issue on our side.
We will come back to you as soon as we have an update on this matter.

Thank you for the report.

1 Like

Thank you @dogan.ay

I don’t know if this will help, but the link to the cards collection on the Related Data section looks to be correct

https://app.forestadmin.com/Qashio/Development | xxx/Clients/data/User/index/record/User/175/has-many/User-all_cards

Hello @muz

I reproduced the issue on my end. The documentation is correct but not the code.
We’ll fix the code on a major version because it’s a breaking change.

Currently you have to change the url in your routes.rb by the following one:

get '/User/:user_id/all_cards' => 'users#all_cards'

I’ll update the documentation soon.

Tell me if it works :slight_smile:

Hello @matthv

I changed the route, and it is still not working, but the errors are different:

Started GET "/forest/User/4/all_cards?timezone=Europe%2FSamara&fields%5BCard%5D=card_...=name&page%5Bnumber%5D=1&page%5Bsize%5D=15&sort=-id" for 127.0.0.1 at 2023-01-12 08:46:33 -0500
Processing by Forest::UsersController#all_cards as JSON
  Parameters: {"timezone"=>"Europe/Samara", "fields"=>{"Card"=>"card_..."name"}, "page"=>{"number"=>"1", "size"=>"15"}, "sort"=>"-id", "user_id"=>"4"}

[2023-01-12 08:46:33] Forest 🌳🌳🌳  Smart Action execution error: undefined method `[]' for nil:NilClass
...
[2023-01-12 08:46:33] Forest 🌳🌳🌳  Smart action execution error: Unable to retrieve the smart action id.

[2023-01-12 08:46:33] Forest 🌳🌳🌳  Find Collection error: Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return"

The rest of the code is unchanged.
Any ideas?

Thanks!

It’s seems not related with the smart relationship.

Could you share your lib/forest_liana/collections/user.rb file

and also the forest namepace section of your routes.rb ? :pray:

class Forest::User
  include ForestLiana::Collection
  collection :User

  # User to Cards HasMany Smart Relationship
  has_many :all_cards, type: ['String'], reference: 'Card.id'

end

# routes:
  namespace :forest do

    # Smart Relationships
    get '/User/:user_id/all_cards' => 'users#all_cards'
  end

I have redacted all of the other actions and routes from there since they all work fine.

I just tested this again by temporarily commenting out all smart fields/actions from the Users collection, as well as commenting out all of the other forest routes, and still failed with the same errors.

The issue comes from our smart_actions_controller.

https://github.com/ForestAdmin/forest-rails/blob/e6611e9c1adc2411439a8d88fa6259bd85b4e183/app/controllers/forest_liana/smart_actions_controller.rb#L16

This is odd because it only works with a smart action route.

Do you have a before_action call or something like that before your custom route?
(maybe where you define user_id)

Could you simplify the controller logic with a basic example like this:

def all_cards
    limit = params['page']['size'].to_i
    offset = (params['page']['number'].to_i - 1) * limit

    users = User.limit(limit).offset(offset)
    count = user.count

    render json : serialize_models(users, meta : {count : count})
end

There is only one before_action in the controller, and I commented that out and tried again, but still failing with the same errors.

# routes:
require 'sidekiq/web'
require 'sidekiq/cron/web'

Rails.application.routes.draw do
  mount Sidekiq::Web => '/sidekiq'

  namespace :forest do

    # Smart Relationships
    get '/User/:user_id/all_cards' => 'users#all_cards'
  end

I also simplified the ‘all_cards’ method, but made no difference.

I even added ‘debugger’ in the method definition in an attempt to debug (see params, etc.), but it looks like it is failing before reaching the method because I am not able to enter debug mode - just displays same errors.

  def all_cards

debugger
    user = User.find(params['user_id'])
...

Thanks again for all your help!

I added this relationship in user.rb:

has_many :cards, through: :user_profiles

And it added a ‘Cards’ collection in the Users Related Data section, which worked, but the internally generated route is:

GET /forest/User/4/relationships/cards?timezone=Europe%2FSamara&fields%5BCard%5D=card...

Both ‘All Cards’ and ‘Cards’ links are the same, so I am not sure why the initial Smart Relationship implementation with the route

get '/User/:user_id/relationships/all_cards' => 'users#all_cards'

doesn’t work as expected?

Just to have more context, a basic has_many relationship has the following format:
/forest/:collection/:id/relationships/:relation_name

but on a smartrelationship the format is
/forest/:collection/:id/:relation_name (which we need to patch in the future)

more information here :

I’m sorry I still can’t reproduce your issue, however since you can’t reach the debugger, it looks like a route conflict.
Can you try to put the route definition /User/:user_id/all_cards first?

Have you also tried to call this route directly with a tool like postman or insomnia rest?

Hi @matthv - apologies for not getting back to you sooner

I commented out everything except for these:

  namespace :forest do

    get '/User/:user_id/all_cards' => 'users#all_cards'

  end
  
  mount ForestLiana::Engine => '/forest'

  root to: 'home#show'
end

and used insomnia to call the route as you suggested, and got the following error:

Routing Error
No route matches [GET] "/User/4/all_cards"

forest_path	GET	/forest/User/:user_id/all_cards(.:format)	   forest/users#all_cards

forest_liana_path		/forest	ForestLiana::Engine

root_path	GET	/	home#show

Thanks again for taking the time to look into this, I appreciate it!

Hi @muz,

The namespace forest adds prefix /forest.
Did you test on insomnia a call on the /forest/User/4/all_cards route?

Also, does the controller /lib/forest_liana/controllers/users_controller.rb extend ForestLiana::ApplicationController ?

Hi @matthv

I think this was the issue - I completely glossed over that part of the guide, and have been using our regular users smart action controller, which extends ForestLiana::SmartActionsController

I am now able to enter debug mode inside the controller method definition, which means the route is working. I will do a complete test later today and will let you know how it goes.

I apologize for wasting your time with this - I should have paid more attention to the docs, and also that the failing route and your earlier links to your source code should have led me to take a closer look at that portion of the code.

Thanks again for all your help, and your patience!

Glad we found the error.
I look forward to your feedback :crossed_fingers:

1 Like

Hi @matthv

I finally got around to testing it properly, and found a couple of issues;

  1. from the docs:
customers = orders.limit(limit).offset(offset).map(&:customer)

This won’t work because map returns an array. I initially did the same thing as well and got this error:

class Forest::UsersSmartRelationshipController < ForestLiana::ApplicationController


  def all_cards
    limit = params['page']['size'].to_i
    offset = (params['page']['number'].to_i - 1) * limit

    user = User.find(params['user_id'])

    user_profiles = user.user_profiles

    cards = user_profiles.limit(limit).offset(offset).map(&:cards)
    # cards = user_profiles.map(&:cards)

    count = cards.count 
    # render json: serialize_models(cards) 
    render json: serialize_models(cards, meta: {count: count})
  end
      
end

# error:
Completed 500 Internal Server Error in 46ms (ActiveRecord: 0.7ms | MongoDB: 0.0ms | Allocations: 10177)
  
NameError (uninitialized constant ForestLiana::UserSpace::ActiveRecordAssociationsCollectionproxySerializer

      Object.const_get(camel_cased_word)
            ^^^^^^^^^^):
  
app/controllers/forest/users_smart_relationship_controller.rb:17:in `all_cards'

The fix was to use a query that returns an ActiveRecord_Relation:

    cards = Card.where(user_profile_id: user_profiles.ids).limit(limit).offset(offset)

That fixed the main issue, and I was able to get All Cards to list the user’s first 15 cards.

The second issue though is that I can’t get the pagination to work properly now - it only returns the first page (first 15 items).

Thanks!